Skip to content

Commit 8aff5b4

Browse files
committed
Merge branch 'PHP-8.1' into PHP-8.2
2 parents 3e3e3b3 + a87ccc7 commit 8aff5b4

File tree

8 files changed

+818
-50
lines changed

8 files changed

+818
-50
lines changed

ext/filter/logical_filters.c

+20-15
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
#define FORMAT_IPV4 4
9090
#define FORMAT_IPV6 6
9191

92-
static int _php_filter_validate_ipv6(char *str, size_t str_len, int ip[8]);
92+
static int _php_filter_validate_ipv6(const char *str, size_t str_len, int ip[8]);
9393

9494
static int php_filter_parse_int(const char *str, size_t str_len, zend_long *ret) { /* {{{ */
9595
zend_long ctx_value;
@@ -580,6 +580,14 @@ static int is_userinfo_valid(zend_string *str)
580580
return 1;
581581
}
582582

583+
static bool php_filter_is_valid_ipv6_hostname(const char *s, size_t l)
584+
{
585+
const char *e = s + l;
586+
const char *t = e - 1;
587+
588+
return *s == '[' && *t == ']' && _php_filter_validate_ipv6(s + 1, l - 2, NULL);
589+
}
590+
583591
void php_filter_validate_url(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
584592
{
585593
php_url *url;
@@ -600,7 +608,7 @@ void php_filter_validate_url(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
600608

601609
if (url->scheme != NULL &&
602610
(zend_string_equals_literal_ci(url->scheme, "http") || zend_string_equals_literal_ci(url->scheme, "https"))) {
603-
char *e, *s, *t;
611+
const char *s;
604612
size_t l;
605613

606614
if (url->host == NULL) {
@@ -609,17 +617,14 @@ void php_filter_validate_url(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
609617

610618
s = ZSTR_VAL(url->host);
611619
l = ZSTR_LEN(url->host);
612-
e = s + l;
613-
t = e - 1;
614-
615-
/* An IPv6 enclosed by square brackets is a valid hostname */
616-
if (*s == '[' && *t == ']' && _php_filter_validate_ipv6((s + 1), l - 2, NULL)) {
617-
php_url_free(url);
618-
return;
619-
}
620620

621-
// Validate domain
622-
if (!_php_filter_validate_domain(ZSTR_VAL(url->host), l, FILTER_FLAG_HOSTNAME)) {
621+
if (
622+
/* An IPv6 enclosed by square brackets is a valid hostname.*/
623+
!php_filter_is_valid_ipv6_hostname(s, l) &&
624+
/* Validate domain.
625+
* This includes a loose check for an IPv4 address. */
626+
!_php_filter_validate_domain(ZSTR_VAL(url->host), l, FILTER_FLAG_HOSTNAME)
627+
) {
623628
php_url_free(url);
624629
RETURN_VALIDATION_FAILED
625630
}
@@ -753,15 +758,15 @@ static int _php_filter_validate_ipv4(char *str, size_t str_len, int *ip) /* {{{
753758
}
754759
/* }}} */
755760

756-
static int _php_filter_validate_ipv6(char *str, size_t str_len, int ip[8]) /* {{{ */
761+
static int _php_filter_validate_ipv6(const char *str, size_t str_len, int ip[8]) /* {{{ */
757762
{
758763
int compressed_pos = -1;
759764
int blocks = 0;
760765
int num, n, i;
761766
char *ipv4;
762-
char *end;
767+
const char *end;
763768
int ip4elm[4];
764-
char *s = str;
769+
const char *s = str;
765770

766771
if (!memchr(str, ':', str_len)) {
767772
return 0;
+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
GHSA-w8qr-v226-r27w
3+
--EXTENSIONS--
4+
filter
5+
--FILE--
6+
<?php
7+
8+
function test(string $input) {
9+
var_dump(filter_var($input, FILTER_VALIDATE_URL));
10+
}
11+
12+
echo "--- These ones should fail ---\n";
13+
test("http://t[est@127.0.0.1");
14+
test("http://t[est@[::1]");
15+
test("http://t[est@[::1");
16+
test("http://t[est@::1]");
17+
test("http://php.net\\@aliyun.com/aaa.do");
18+
test("http://test[@2001:db8:3333:4444:5555:6666:1.2.3.4]");
19+
test("http://te[st@2001:db8:3333:4444:5555:6666:1.2.3.4]");
20+
test("http://te[st@2001:db8:3333:4444:5555:6666:1.2.3.4");
21+
22+
echo "--- These ones should work ---\n";
23+
test("http://test@127.0.0.1");
24+
test("http://test@[2001:db8:3333:4444:5555:6666:1.2.3.4]");
25+
test("http://test@[::1]");
26+
27+
?>
28+
--EXPECT--
29+
--- These ones should fail ---
30+
bool(false)
31+
bool(false)
32+
bool(false)
33+
bool(false)
34+
bool(false)
35+
bool(false)
36+
bool(false)
37+
bool(false)
38+
--- These ones should work ---
39+
string(21) "http://test@127.0.0.1"
40+
string(50) "http://test@[2001:db8:3333:4444:5555:6666:1.2.3.4]"
41+
string(17) "http://test@[::1]"

ext/standard/proc_open.c

+25-34
Original file line numberDiff line numberDiff line change
@@ -546,48 +546,39 @@ static void append_win_escaped_arg(smart_str *str, zend_string *arg, bool is_cmd
546546
smart_str_appendc(str, '"');
547547
}
548548

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);
549+
static bool is_executed_by_cmd(const char *prog_name, size_t prog_name_length)
550+
{
551+
size_t out_len;
552+
WCHAR long_name[MAX_PATH];
553+
WCHAR full_name[MAX_PATH];
554+
LPWSTR file_part = NULL;
552555

553-
if (suffix_len > str_len) {
554-
return -1; /* Suffix is longer than string, cannot match. */
555-
}
556+
wchar_t *prog_name_wide = php_win32_cp_conv_any_to_w(prog_name, prog_name_length, &out_len);
556557

557-
/* Compare the end of the string with the suffix, ignoring case. */
558-
return _stricmp(str + (str_len - suffix_len), suffix);
559-
}
558+
if (GetLongPathNameW(prog_name_wide, long_name, MAX_PATH) == 0) {
559+
/* This can fail for example with ERROR_FILE_NOT_FOUND (short path resolution only works for existing files)
560+
* in which case we'll pass the path verbatim to the FullPath transformation. */
561+
lstrcpynW(long_name, prog_name_wide, MAX_PATH);
562+
}
560563

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-
}
564+
free(prog_name_wide);
565+
prog_name_wide = NULL;
568566

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;
567+
if (GetFullPathNameW(long_name, MAX_PATH, full_name, &file_part) == 0 || file_part == NULL) {
568+
return false;
574569
}
575570

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, '.');
571+
bool uses_cmd = false;
572+
if (_wcsicmp(file_part, L"cmd.exe") == 0 || _wcsicmp(file_part, L"cmd") == 0) {
573+
uses_cmd = true;
580574
} 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;
575+
const WCHAR *extension_dot = wcsrchr(file_part, L'.');
576+
if (extension_dot && (_wcsicmp(extension_dot, L".bat") == 0 || _wcsicmp(extension_dot, L".cmd") == 0)) {
577+
uses_cmd = true;
578+
}
587579
}
588580

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;
581+
return uses_cmd;
591582
}
592583

593584
static zend_string *create_win_command_from_args(HashTable *args)
@@ -606,7 +597,7 @@ static zend_string *create_win_command_from_args(HashTable *args)
606597
}
607598

608599
if (is_prog_name) {
609-
is_cmd_execution = is_executed_by_cmd(ZSTR_VAL(arg_str));
600+
is_cmd_execution = is_executed_by_cmd(ZSTR_VAL(arg_str), ZSTR_LEN(arg_str));
610601
} else {
611602
smart_str_appendc(&str, ' ');
612603
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
--TEST--
2+
GHSA-9fcc-425m-g385 - bypass CVE-2024-1874 - batch file variation
3+
--SKIPIF--
4+
<?php
5+
if( substr(PHP_OS, 0, 3) != "WIN" )
6+
die('skip Run only on Windows');
7+
if (getenv("SKIP_SLOW_TESTS")) die("skip slow test");
8+
?>
9+
--FILE--
10+
<?php
11+
12+
$batch_file_content = <<<EOT
13+
@echo off
14+
powershell -Command "Write-Output '%0%'"
15+
powershell -Command "Write-Output '%1%'"
16+
EOT;
17+
$batch_file_path = __DIR__ . '/ghsa-9fcc-425m-g385_001.bat';
18+
19+
file_put_contents($batch_file_path, $batch_file_content);
20+
21+
$descriptorspec = [STDIN, STDOUT, STDOUT];
22+
23+
$proc = proc_open([$batch_file_path . ".", "\"&notepad.exe"], $descriptorspec, $pipes);
24+
proc_close($proc);
25+
$proc = proc_open([$batch_file_path . " ", "\"&notepad.exe"], $descriptorspec, $pipes);
26+
proc_close($proc);
27+
$proc = proc_open([$batch_file_path . ". ", "\"&notepad.exe"], $descriptorspec, $pipes);
28+
proc_close($proc);
29+
$proc = proc_open([$batch_file_path . ". ... ", "\"&notepad.exe"], $descriptorspec, $pipes);
30+
proc_close($proc);
31+
$proc = proc_open([$batch_file_path . ". ... . ", "\"&notepad.exe"], $descriptorspec, $pipes);
32+
proc_close($proc);
33+
$proc = proc_open([$batch_file_path . ". ... . .", "\"&notepad.exe"], $descriptorspec, $pipes);
34+
proc_close($proc);
35+
proc_open([$batch_file_path . ". .\\.. . .", "\"&notepad.exe"], $descriptorspec, $pipes);
36+
37+
?>
38+
--EXPECTF--
39+
'"%sghsa-9fcc-425m-g385_001.bat."' is not recognized as an internal or external command,
40+
operable program or batch file.
41+
%sghsa-9fcc-425m-g385_001.bat
42+
"&notepad.exe
43+
%sghsa-9fcc-425m-g385_001.bat.
44+
"&notepad.exe
45+
%sghsa-9fcc-425m-g385_001.bat. ...
46+
"&notepad.exe
47+
%sghsa-9fcc-425m-g385_001.bat. ... .
48+
"&notepad.exe
49+
'"%sghsa-9fcc-425m-g385_001.bat. ... . ."' is not recognized as an internal or external command,
50+
operable program or batch file.
51+
52+
Warning: proc_open(): CreateProcess failed, error code: 2 in %s on line %d
53+
--CLEAN--
54+
<?php
55+
@unlink(__DIR__ . '/ghsa-9fcc-425m-g385_001.bat');
56+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
--TEST--
2+
GHSA-9fcc-425m-g385 - bypass CVE-2024-1874 - cmd.exe variation
3+
--SKIPIF--
4+
<?php
5+
if( substr(PHP_OS, 0, 3) != "WIN" )
6+
die('skip Run only on Windows');
7+
if (getenv("SKIP_SLOW_TESTS")) die("skip slow test");
8+
?>
9+
--FILE--
10+
<?php
11+
12+
$batch_file_content = <<<EOT
13+
@echo off
14+
powershell -Command "Write-Output '%0%'"
15+
powershell -Command "Write-Output '%1%'"
16+
EOT;
17+
$batch_file_path = __DIR__ . '/ghsa-9fcc-425m-g385_002.bat';
18+
19+
file_put_contents($batch_file_path, $batch_file_content);
20+
21+
$descriptorspec = [STDIN, STDOUT, STDOUT];
22+
23+
$proc = proc_open(["cmd.exe", "/c", $batch_file_path, "\"&notepad.exe"], $descriptorspec, $pipes);
24+
proc_close($proc);
25+
$proc = proc_open(["cmd.exe ", "/c", $batch_file_path, "\"&notepad.exe"], $descriptorspec, $pipes);
26+
proc_close($proc);
27+
$proc = proc_open(["cmd.exe. ", "/c", $batch_file_path, "\"&notepad.exe"], $descriptorspec, $pipes);
28+
proc_close($proc);
29+
$proc = proc_open(["cmd.exe. ... ", "/c", $batch_file_path, "\"&notepad.exe"], $descriptorspec, $pipes);
30+
proc_close($proc);
31+
$proc = proc_open(["\\cmd.exe. ... ", "/c", $batch_file_path, "\"&notepad.exe"], $descriptorspec, $pipes);
32+
33+
$proc = proc_open(["cmd", "/c", $batch_file_path, "\"&notepad.exe"], $descriptorspec, $pipes);
34+
proc_close($proc);
35+
$proc = proc_open(["cmd ", "/c", $batch_file_path, "\"&notepad.exe"], $descriptorspec, $pipes);
36+
proc_close($proc);
37+
$proc = proc_open(["cmd. ", "/c", $batch_file_path, "\"&notepad.exe"], $descriptorspec, $pipes);
38+
$proc = proc_open(["cmd. ... ", "/c", $batch_file_path, "\"&notepad.exe"], $descriptorspec, $pipes);
39+
$proc = proc_open(["\\cmd. ... ", "/c", $batch_file_path, "\"&notepad.exe"], $descriptorspec, $pipes);
40+
41+
?>
42+
--EXPECTF--
43+
%sghsa-9fcc-425m-g385_002.bat
44+
"&notepad.exe
45+
%sghsa-9fcc-425m-g385_002.bat
46+
"&notepad.exe
47+
%sghsa-9fcc-425m-g385_002.bat
48+
"&notepad.exe
49+
%sghsa-9fcc-425m-g385_002.bat
50+
"&notepad.exe
51+
52+
Warning: proc_open(): CreateProcess failed, error code: 2 in %s on line %d
53+
%sghsa-9fcc-425m-g385_002.bat
54+
"&notepad.exe
55+
%sghsa-9fcc-425m-g385_002.bat
56+
"&notepad.exe
57+
58+
Warning: proc_open(): CreateProcess failed, error code: 2 in %s on line %d
59+
60+
Warning: proc_open(): CreateProcess failed, error code: 2 in %s on line %d
61+
62+
Warning: proc_open(): CreateProcess failed, error code: 2 in %s on line %d
63+
--CLEAN--
64+
<?php
65+
@unlink(__DIR__ . '/ghsa-9fcc-425m-g385_002.bat');
66+
?>

0 commit comments

Comments
 (0)