mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
Merge branch 'PHP-8.4'
* PHP-8.4: Harden proc_open() against cmd.exe hijacking
This commit is contained in:
commit
3ee522a31d
5 changed files with 98 additions and 3 deletions
|
@ -7,3 +7,7 @@ ext\standard\url_scanner_ex.c: ext\standard\url_scanner_ex.re
|
||||||
$(RE2C) $(RE2C_FLAGS) -b -o ext/standard/url_scanner_ex.c ext/standard/url_scanner_ex.re
|
$(RE2C) $(RE2C_FLAGS) -b -o ext/standard/url_scanner_ex.c ext/standard/url_scanner_ex.re
|
||||||
|
|
||||||
$(BUILD_DIR)\ext\standard\basic_functions.obj: $(PHP_SRC_DIR)\Zend\zend_language_parser.h
|
$(BUILD_DIR)\ext\standard\basic_functions.obj: $(PHP_SRC_DIR)\Zend\zend_language_parser.h
|
||||||
|
|
||||||
|
$(PHP_SRC_DIR)\ext\standard\tests\helpers\bad_cmd.exe: $(PHP_SRC_DIR)\ext\standard\tests\helpers\bad_cmd.c
|
||||||
|
cd $(PHP_SRC_DIR)\ext\standard\tests\helpers
|
||||||
|
$(PHP_CL) /nologo bad_cmd.c
|
||||||
|
|
|
@ -698,22 +698,77 @@ static void init_process_info(PROCESS_INFORMATION *pi)
|
||||||
memset(&pi, 0, sizeof(pi));
|
memset(&pi, 0, sizeof(pi));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* on success, returns length of *comspec, which then needs to be efree'd by caller */
|
||||||
|
static size_t find_comspec_nt(wchar_t **comspec)
|
||||||
|
{
|
||||||
|
zend_string *path = NULL;
|
||||||
|
wchar_t *pathw = NULL;
|
||||||
|
wchar_t *bufp = NULL;
|
||||||
|
DWORD buflen = MAX_PATH, len = 0;
|
||||||
|
|
||||||
|
path = php_getenv("PATH", 4);
|
||||||
|
if (path == NULL) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
pathw = php_win32_cp_any_to_w(ZSTR_VAL(path));
|
||||||
|
if (pathw == NULL) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
bufp = emalloc(buflen * sizeof(wchar_t));
|
||||||
|
do {
|
||||||
|
/* the first call to SearchPathW() fails if the buffer is too small,
|
||||||
|
* what is unlikely but possible; to avoid an explicit second call to
|
||||||
|
* SeachPathW() and the error handling, we're looping */
|
||||||
|
len = SearchPathW(pathw, L"cmd.exe", NULL, buflen, bufp, NULL);
|
||||||
|
if (len == 0) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (len < buflen) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
buflen = len;
|
||||||
|
bufp = erealloc(bufp, buflen * sizeof(wchar_t));
|
||||||
|
} while (1);
|
||||||
|
*comspec = bufp;
|
||||||
|
|
||||||
|
out:
|
||||||
|
if (path != NULL) {
|
||||||
|
zend_string_release(path);
|
||||||
|
}
|
||||||
|
if (pathw != NULL) {
|
||||||
|
free(pathw);
|
||||||
|
}
|
||||||
|
if (bufp != NULL && bufp != *comspec) {
|
||||||
|
efree(bufp);
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
static zend_result convert_command_to_use_shell(wchar_t **cmdw, size_t cmdw_len)
|
static zend_result convert_command_to_use_shell(wchar_t **cmdw, size_t cmdw_len)
|
||||||
{
|
{
|
||||||
size_t len = sizeof(COMSPEC_NT) + sizeof(" /s /c ") + cmdw_len + 3;
|
wchar_t *comspec;
|
||||||
|
size_t len = find_comspec_nt(&comspec);
|
||||||
|
if (len == 0) {
|
||||||
|
php_error_docref(NULL, E_WARNING, "Command conversion failed");
|
||||||
|
return FAILURE;
|
||||||
|
}
|
||||||
|
len += sizeof(" /s /c ") + cmdw_len + 3;
|
||||||
wchar_t *cmdw_shell = (wchar_t *)malloc(len * sizeof(wchar_t));
|
wchar_t *cmdw_shell = (wchar_t *)malloc(len * sizeof(wchar_t));
|
||||||
|
|
||||||
if (cmdw_shell == NULL) {
|
if (cmdw_shell == NULL) {
|
||||||
|
efree(comspec);
|
||||||
php_error_docref(NULL, E_WARNING, "Command conversion failed");
|
php_error_docref(NULL, E_WARNING, "Command conversion failed");
|
||||||
return FAILURE;
|
return FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_snwprintf(cmdw_shell, len, L"%hs /s /c \"%s\"", COMSPEC_NT, *cmdw) == -1) {
|
if (_snwprintf(cmdw_shell, len, L"%s /s /c \"%s\"", comspec, *cmdw) == -1) {
|
||||||
|
efree(comspec);
|
||||||
free(cmdw_shell);
|
free(cmdw_shell);
|
||||||
php_error_docref(NULL, E_WARNING, "Command conversion failed");
|
php_error_docref(NULL, E_WARNING, "Command conversion failed");
|
||||||
return FAILURE;
|
return FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
efree(comspec);
|
||||||
free(*cmdw);
|
free(*cmdw);
|
||||||
*cmdw = cmdw_shell;
|
*cmdw = cmdw_shell;
|
||||||
|
|
||||||
|
|
28
ext/standard/tests/general_functions/proc_open_cmd.phpt
Normal file
28
ext/standard/tests/general_functions/proc_open_cmd.phpt
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
--TEST--
|
||||||
|
Harden against cmd.exe hijacking
|
||||||
|
--SKIPIF--
|
||||||
|
<?php
|
||||||
|
if (PHP_OS_FAMILY !== "Windows") die("skip only for Windows");
|
||||||
|
?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
copy(__DIR__ . "/../helpers/bad_cmd.exe", "cmd.exe");
|
||||||
|
$spec = [["pipe", "r"], ["pipe", "w"], ["pipe", "w"]];
|
||||||
|
var_dump($proc = proc_open("@echo hello", $spec, $pipes, null));
|
||||||
|
$read = [$pipes[1], $pipes[2]];
|
||||||
|
$write = $except = null;
|
||||||
|
if (($num = stream_select($read, $write, $except, 1000)) === false) {
|
||||||
|
echo "stream_select() failed\n";
|
||||||
|
} elseif ($num > 0) {
|
||||||
|
foreach ($read as $stream) {
|
||||||
|
fpassthru($stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
--EXPECTF--
|
||||||
|
resource(%d) of type (process)
|
||||||
|
hello
|
||||||
|
--CLEAN--
|
||||||
|
<?php
|
||||||
|
@unlink("cmd.exe");
|
||||||
|
?>
|
7
ext/standard/tests/helpers/bad_cmd.c
Normal file
7
ext/standard/tests/helpers/bad_cmd.c
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
printf("pwnd!\n");
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -54,7 +54,7 @@ DEBUGGER_CMD=
|
||||||
DEBUGGER_ARGS=
|
DEBUGGER_ARGS=
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
all: generated_files $(EXT_TARGETS) $(PECL_TARGETS) $(SAPI_TARGETS)
|
all: generated_files $(EXT_TARGETS) $(PECL_TARGETS) $(SAPI_TARGETS) test_helpers
|
||||||
|
|
||||||
build_dirs: $(BUILD_DIR) $(BUILD_DIRS_SUB) $(BUILD_DIR_DEV)
|
build_dirs: $(BUILD_DIR) $(BUILD_DIRS_SUB) $(BUILD_DIR_DEV)
|
||||||
|
|
||||||
|
@ -184,6 +184,7 @@ clean-pgo: clean-all
|
||||||
-del /f /q $(BUILD_DIR)\$(DIST_ZIP_PECL)
|
-del /f /q $(BUILD_DIR)\$(DIST_ZIP_PECL)
|
||||||
-del /f /q $(BUILD_DIR)\$(DIST_ZIP_TEST_PACK)
|
-del /f /q $(BUILD_DIR)\$(DIST_ZIP_TEST_PACK)
|
||||||
|
|
||||||
|
test_helpers: $(PHP_SRC_DIR)\ext\standard\tests\helpers\bad_cmd.exe
|
||||||
|
|
||||||
!if $(PHP_TEST_INI_PATH) == ""
|
!if $(PHP_TEST_INI_PATH) == ""
|
||||||
test: set-tmp-env
|
test: set-tmp-env
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue