Skip to content

Commit e64bffc

Browse files
committed
Merge tag 'php-8.1.28' into was-8.1.x
Tag for php-8.1.28
2 parents 74f5943 + 9119509 commit e64bffc

File tree

13 files changed

+288
-25
lines changed

13 files changed

+288
-25
lines changed

.github/actions/setup-caddy/action.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ runs:
33
using: composite
44
steps:
55
- shell: bash
6+
env:
7+
GH_TOKEN: ${{ github.token }}
68
run: |
79
set -x
8-
sudo curl 'https://caddyserver.com/api/download?os=linux&arch=amd64' -o /usr/bin/caddy
10+
gh release -R caddyserver/caddy download --pattern 'caddy_*_linux_amd64.tar.gz' -O - | sudo tar -xz -C /usr/bin caddy
911
sudo chmod +x /usr/bin/caddy
1012
sudo caddy start --config ext/curl/tests/Caddyfile

NEWS

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
PHP NEWS
22
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
3+
11 Apr 2024, PHP 8.1.28
4+
5+
- Standard:
6+
. Fixed bug GHSA-pc52-254m-w9w7 (Command injection via array-ish $command
7+
parameter of proc_open). (CVE-2024-1874) (Jakub Zelenka)
8+
. Fixed bug GHSA-wpj3-hf5j-x4v4 (__Host-/__Secure- cookie bypass due to
9+
partial CVE-2022-31629 fix). (CVE-2024-2756) (nielsdos)
10+
. Fixed bug GHSA-h746-cjrr-wfmr (password_verify can erroneously return true,
11+
opening ATO risk). (CVE-2024-3096) (Jakub Zelenka)
12+
313
21 Dec 2023, PHP 8.1.27
414

515
- Core:

Zend/zend.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
#ifndef ZEND_H
2121
#define ZEND_H
2222

23-
#define ZEND_VERSION "4.1.27"
23+
#define ZEND_VERSION "4.1.28"
2424

2525
#define ZEND_ENGINE_3
2626

configure.ac

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ dnl Basic autoconf initialization, generation of config.nice.
1717
dnl ----------------------------------------------------------------------------
1818

1919
AC_PREREQ([2.68])
20-
AC_INIT([PHP],[8.1.27],[https://github.com/php/php-src/issues],[php],[https://www.php.net])
20+
AC_INIT([PHP],[8.1.28],[https://github.com/php/php-src/issues],[php],[https://www.php.net])
2121
AC_CONFIG_SRCDIR([main/php_version.h])
2222
AC_CONFIG_AUX_DIR([build])
2323
AC_PRESERVE_HELP_ORDER

ext/standard/password.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,11 @@ static zend_string* php_password_bcrypt_hash(const zend_string *password, zend_a
184184
zval *zcost;
185185
zend_long cost = PHP_PASSWORD_BCRYPT_COST;
186186

187+
if (memchr(ZSTR_VAL(password), '\0', ZSTR_LEN(password))) {
188+
zend_value_error("Bcrypt password must not contain null character");
189+
return NULL;
190+
}
191+
187192
if (options && (zcost = zend_hash_str_find(options, "cost", sizeof("cost")-1)) != NULL) {
188193
cost = zval_get_long(zcost);
189194
}

ext/standard/proc_open.c

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -492,11 +492,32 @@ static void append_backslashes(smart_str *str, size_t num_bs)
492492
}
493493
}
494494

495-
/* See https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments */
496-
static void append_win_escaped_arg(smart_str *str, zend_string *arg)
495+
const char *special_chars = "()!^\"<>&|%";
496+
497+
static bool is_special_character_present(const zend_string *arg)
498+
{
499+
for (size_t i = 0; i < ZSTR_LEN(arg); ++i) {
500+
if (strchr(special_chars, ZSTR_VAL(arg)[i]) != NULL) {
501+
return true;
502+
}
503+
}
504+
return false;
505+
}
506+
507+
/* See https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments and
508+
* https://learn.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way */
509+
static void append_win_escaped_arg(smart_str *str, zend_string *arg, bool is_cmd_argument)
497510
{
498511
size_t num_bs = 0;
512+
bool has_special_character = false;
499513

514+
if (is_cmd_argument) {
515+
has_special_character = is_special_character_present(arg);
516+
if (has_special_character) {
517+
/* Escape double quote with ^ if executed by cmd.exe. */
518+
smart_str_appendc(str, '^');
519+
}
520+
}
500521
smart_str_appendc(str, '"');
501522
for (size_t i = 0; i < ZSTR_LEN(arg); ++i) {
502523
char c = ZSTR_VAL(arg)[i];
@@ -510,18 +531,71 @@ static void append_win_escaped_arg(smart_str *str, zend_string *arg)
510531
num_bs = num_bs * 2 + 1;
511532
}
512533
append_backslashes(str, num_bs);
534+
if (has_special_character && strchr(special_chars, c) != NULL) {
535+
/* Escape special chars with ^ if executed by cmd.exe. */
536+
smart_str_appendc(str, '^');
537+
}
513538
smart_str_appendc(str, c);
514539
num_bs = 0;
515540
}
516541
append_backslashes(str, num_bs * 2);
542+
if (has_special_character) {
543+
/* Escape double quote with ^ if executed by cmd.exe. */
544+
smart_str_appendc(str, '^');
545+
}
517546
smart_str_appendc(str, '"');
518547
}
519548

549+
static inline int stricmp_end(const char* suffix, const char* str) {
550+
size_t suffix_len = strlen(suffix);
551+
size_t str_len = strlen(str);
552+
553+
if (suffix_len > str_len) {
554+
return -1; /* Suffix is longer than string, cannot match. */
555+
}
556+
557+
/* Compare the end of the string with the suffix, ignoring case. */
558+
return _stricmp(str + (str_len - suffix_len), suffix);
559+
}
560+
561+
static bool is_executed_by_cmd(const char *prog_name)
562+
{
563+
/* If program name is cmd.exe, then return true. */
564+
if (_stricmp("cmd.exe", prog_name) == 0 || _stricmp("cmd", prog_name) == 0
565+
|| stricmp_end("\\cmd.exe", prog_name) == 0 || stricmp_end("\\cmd", prog_name) == 0) {
566+
return true;
567+
}
568+
569+
/* Find the last occurrence of the directory separator (backslash or forward slash). */
570+
char *last_separator = strrchr(prog_name, '\\');
571+
char *last_separator_fwd = strrchr(prog_name, '/');
572+
if (last_separator_fwd && (!last_separator || last_separator < last_separator_fwd)) {
573+
last_separator = last_separator_fwd;
574+
}
575+
576+
/* Find the last dot in the filename after the last directory separator. */
577+
char *extension = NULL;
578+
if (last_separator != NULL) {
579+
extension = strrchr(last_separator, '.');
580+
} else {
581+
extension = strrchr(prog_name, '.');
582+
}
583+
584+
if (extension == NULL || extension == prog_name) {
585+
/* No file extension found, it is not batch file. */
586+
return false;
587+
}
588+
589+
/* Check if the file extension is ".bat" or ".cmd" which is always executed by cmd.exe. */
590+
return _stricmp(extension, ".bat") == 0 || _stricmp(extension, ".cmd") == 0;
591+
}
592+
520593
static zend_string *create_win_command_from_args(HashTable *args)
521594
{
522595
smart_str str = {0};
523596
zval *arg_zv;
524-
bool is_prog_name = 1;
597+
bool is_prog_name = true;
598+
bool is_cmd_execution = false;
525599
int elem_num = 0;
526600

527601
ZEND_HASH_FOREACH_VAL(args, arg_zv) {
@@ -531,11 +605,13 @@ static zend_string *create_win_command_from_args(HashTable *args)
531605
return NULL;
532606
}
533607

534-
if (!is_prog_name) {
608+
if (is_prog_name) {
609+
is_cmd_execution = is_executed_by_cmd(ZSTR_VAL(arg_str));
610+
} else {
535611
smart_str_appendc(&str, ' ');
536612
}
537613

538-
append_win_escaped_arg(&str, arg_str);
614+
append_win_escaped_arg(&str, arg_str, !is_prog_name && is_cmd_execution);
539615

540616
is_prog_name = 0;
541617
zend_string_release(arg_str);
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
GHSA-54hq-v5wp-fqgv - proc_open does not correctly escape args for bat files
3+
--SKIPIF--
4+
<?php
5+
if( substr(PHP_OS, 0, 3) != "WIN" )
6+
die('skip Run only on Windows');
7+
?>
8+
--FILE--
9+
<?php
10+
11+
$batch_file_content = <<<EOT
12+
@echo off
13+
powershell -Command "Write-Output '%1%'"
14+
EOT;
15+
$batch_file_path = __DIR__ . '/ghsa-54hq-v5wp-fqgv.bat';
16+
17+
file_put_contents($batch_file_path, $batch_file_content);
18+
19+
$descriptorspec = [STDIN, STDOUT, STDOUT];
20+
$proc = proc_open([$batch_file_path, "\"&notepad.exe"], $descriptorspec, $pipes);
21+
proc_close($proc);
22+
23+
?>
24+
--EXPECT--
25+
"&notepad.exe
26+
--CLEAN--
27+
<?php
28+
@unlink(__DIR__ . '/ghsa-54hq-v5wp-fqgv.bat');
29+
?>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
GHSA-54hq-v5wp-fqgv - proc_open does not correctly escape args for cmd files
3+
--SKIPIF--
4+
<?php
5+
if( substr(PHP_OS, 0, 3) != "WIN" )
6+
die('skip Run only on Windows');
7+
?>
8+
--FILE--
9+
<?php
10+
11+
$batch_file_content = <<<EOT
12+
@echo off
13+
powershell -Command "Write-Output '%1%'"
14+
EOT;
15+
$batch_file_path = __DIR__ . '/ghsa-54hq-v5wp-fqgv.cmd';
16+
17+
file_put_contents($batch_file_path, $batch_file_content);
18+
19+
$descriptorspec = [STDIN, STDOUT, STDOUT];
20+
$proc = proc_open([$batch_file_path, "\"&notepad<>^()!.exe"], $descriptorspec, $pipes);
21+
proc_close($proc);
22+
23+
?>
24+
--EXPECT--
25+
"&notepad<>^()!.exe
26+
--CLEAN--
27+
<?php
28+
@unlink(__DIR__ . '/ghsa-54hq-v5wp-fqgv.cmd');
29+
?>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
GHSA-54hq-v5wp-fqgv - proc_open does not correctly escape args for cmd executing batch files
3+
--SKIPIF--
4+
<?php
5+
if( substr(PHP_OS, 0, 3) != "WIN" )
6+
die('skip Run only on Windows');
7+
?>
8+
--FILE--
9+
<?php
10+
11+
$batch_file_content = <<<EOT
12+
@echo off
13+
powershell -Command "Write-Output '%1%'"
14+
EOT;
15+
$batch_file_path = __DIR__ . '/ghsa-54hq-v5wp-fqgv.bat';
16+
17+
file_put_contents($batch_file_path, $batch_file_content);
18+
19+
$descriptorspec = [STDIN, STDOUT, STDOUT];
20+
$proc = proc_open(["cmd.exe", "/c", $batch_file_path, "\"&notepad.exe"], $descriptorspec, $pipes);
21+
proc_close($proc);
22+
23+
?>
24+
--EXPECT--
25+
"&notepad.exe
26+
--CLEAN--
27+
<?php
28+
@unlink(__DIR__ . '/ghsa-54hq-v5wp-fqgv.bat');
29+
?>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
--TEST--
2+
ghsa-wpj3-hf5j-x4v4 (__Host-/__Secure- cookie bypass due to partial CVE-2022-31629 fix)
3+
--COOKIE--
4+
..Host-test=ignore_1;
5+
._Host-test=ignore_2;
6+
.[Host-test=ignore_3;
7+
_.Host-test=ignore_4;
8+
__Host-test=ignore_5;
9+
_[Host-test=ignore_6;
10+
[.Host-test=ignore_7;
11+
[_Host-test=ignore_8;
12+
[[Host-test=ignore_9;
13+
..Host-test[]=ignore_10;
14+
._Host-test[]=ignore_11;
15+
.[Host-test[]=ignore_12;
16+
_.Host-test[]=ignore_13;
17+
__Host-test[]=legitimate_14;
18+
_[Host-test[]=legitimate_15;
19+
[.Host-test[]=ignore_16;
20+
[_Host-test[]=ignore_17;
21+
[[Host-test[]=ignore_18;
22+
..Secure-test=ignore_1;
23+
._Secure-test=ignore_2;
24+
.[Secure-test=ignore_3;
25+
_.Secure-test=ignore_4;
26+
__Secure-test=ignore_5;
27+
_[Secure-test=ignore_6;
28+
[.Secure-test=ignore_7;
29+
[_Secure-test=ignore_8;
30+
[[Secure-test=ignore_9;
31+
..Secure-test[]=ignore_10;
32+
._Secure-test[]=ignore_11;
33+
.[Secure-test[]=ignore_12;
34+
_.Secure-test[]=ignore_13;
35+
__Secure-test[]=legitimate_14;
36+
_[Secure-test[]=legitimate_15;
37+
[.Secure-test[]=ignore_16;
38+
[_Secure-test[]=ignore_17;
39+
[[Secure-test[]=ignore_18;
40+
--FILE--
41+
<?php
42+
var_dump($_COOKIE);
43+
?>
44+
--EXPECT--
45+
array(3) {
46+
["__Host-test"]=>
47+
array(1) {
48+
[0]=>
49+
string(13) "legitimate_14"
50+
}
51+
["_"]=>
52+
array(2) {
53+
["Host-test["]=>
54+
string(13) "legitimate_15"
55+
["Secure-test["]=>
56+
string(13) "legitimate_15"
57+
}
58+
["__Secure-test"]=>
59+
array(1) {
60+
[0]=>
61+
string(13) "legitimate_14"
62+
}
63+
}

0 commit comments

Comments
 (0)