Skip to content

Commit 6576038

Browse files
Jan-Eremicollet
authored andcommitted
Fix GHSA-9fcc-425m-g385: bypass CVE-2024-1874
1 parent ec1d5e6 commit 6576038

File tree

4 files changed

+697
-34
lines changed

4 files changed

+697
-34
lines changed

ext/standard/proc_open.c

Lines changed: 25 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -474,48 +474,39 @@ static void append_win_escaped_arg(smart_string *str, char *arg, BOOL is_cmd_arg
474474
smart_string_appendc(str, '"');
475475
}
476476

477-
static BOOL stricmp_end(const char* suffix, const char* str) {
478-
size_t suffix_len = strlen(suffix);
479-
size_t str_len = strlen(str);
477+
static BOOL is_executed_by_cmd(const char *prog_name, size_t prog_name_length)
478+
{
479+
size_t out_len;
480+
WCHAR long_name[MAX_PATH];
481+
WCHAR full_name[MAX_PATH];
482+
LPWSTR file_part = NULL;
480483

481-
if (suffix_len > str_len) {
482-
return -1; /* Suffix is longer than string, cannot match. */
483-
}
484+
wchar_t *prog_name_wide = php_win32_cp_conv_any_to_w(prog_name, prog_name_length, &out_len);
484485

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

489-
static BOOL is_executed_by_cmd(const char *prog_name)
490-
{
491-
/* If program name is cmd.exe, then return true. */
492-
if (_stricmp("cmd.exe", prog_name) == 0 || _stricmp("cmd", prog_name) == 0
493-
|| stricmp_end("\\cmd.exe", prog_name) == 0 || stricmp_end("\\cmd", prog_name) == 0) {
494-
return 1;
495-
}
492+
free(prog_name_wide);
493+
prog_name_wide = NULL;
496494

497-
/* Find the last occurrence of the directory separator (backslash or forward slash). */
498-
char *last_separator = strrchr(prog_name, '\\');
499-
char *last_separator_fwd = strrchr(prog_name, '/');
500-
if (last_separator_fwd && (!last_separator || last_separator < last_separator_fwd)) {
501-
last_separator = last_separator_fwd;
495+
if (GetFullPathNameW(long_name, MAX_PATH, full_name, &file_part) == 0 || file_part == NULL) {
496+
return 0;
502497
}
503498

504-
/* Find the last dot in the filename after the last directory separator. */
505-
char *extension = NULL;
506-
if (last_separator != NULL) {
507-
extension = strrchr(last_separator, '.');
499+
BOOL uses_cmd = 0;
500+
if (_wcsicmp(file_part, L"cmd.exe") == 0 || _wcsicmp(file_part, L"cmd") == 0) {
501+
uses_cmd = 1;
508502
} else {
509-
extension = strrchr(prog_name, '.');
510-
}
511-
512-
if (extension == NULL || extension == prog_name) {
513-
/* No file extension found, it is not batch file. */
514-
return 0;
503+
const WCHAR *extension_dot = wcsrchr(file_part, L'.');
504+
if (extension_dot && (_wcsicmp(extension_dot, L".bat") == 0 || _wcsicmp(extension_dot, L".cmd") == 0)) {
505+
uses_cmd = 1;
506+
}
515507
}
516508

517-
/* Check if the file extension is ".bat" or ".cmd" which is always executed by cmd.exe. */
518-
return (_stricmp(extension, ".bat") == 0 || _stricmp(extension, ".cmd") == 0) ? 1 : 0;
509+
return uses_cmd;
519510
}
520511

521512
static char *create_win_command_from_args(HashTable *args) {
@@ -533,7 +524,7 @@ static char *create_win_command_from_args(HashTable *args) {
533524
}
534525

535526
if (is_prog_name) {
536-
is_cmd_execution = is_executed_by_cmd(ZSTR_VAL(arg_str));
527+
is_cmd_execution = is_executed_by_cmd(ZSTR_VAL(arg_str), ZSTR_LEN(arg_str));
537528
} else {
538529
smart_string_appendc(&str, ' ');
539530
}
Lines changed: 56 additions & 0 deletions
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+
?>
Lines changed: 66 additions & 0 deletions
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)