首页 » PHP教程 » linux履行php文件技巧_敕令实行底层事理商量PHP三

linux履行php文件技巧_敕令实行底层事理商量PHP三

访客 2024-10-27 0

扫一扫用手机浏览

文章目录 [+]

序言

针对不同平台/措辞下的命令实行是不相同的,存在很大的差异性。
因此,这里对不同平台/措辞下的命令实行函数进行深入的探究剖析。

文章开头会对不同平台(Linux、Windows)下:终真个指令实行、措辞(PHP、Java、Python)的命令实行进行先容剖析。
后面,紧张以PHP措辞为工具,针对不同平台,对命令实行函数进行底层深入剖析,这个过程包括:环境准备、PHP内核源码的编译、运行、调试、审计等,其它措辞剖析事理思路类似。

linux履行php文件技巧_敕令实行底层事理商量PHP三

该系列剖析文章紧张分为四部分,如下:

linux履行php文件技巧_敕令实行底层事理商量PHP三
(图片来自网络侵删)
第一部分:命令实行底层事理探究-PHP (一)

针对不同平台(Linux、Windows)下:终真个指令实行、措辞(PHP、Java、Python)的命令实行进行先容剖析。

第二部分:命令实行底层事理探究-PHP (二)

紧张以PHP措辞为工具,针对不同平台,进行环境准备、PHP内核源码的编译、运行、调试等。

第三部分:命令实行底层事理探究-PHP (三)

针对Windows平台下,PHP命令实行函数的底层事理剖析。

第四部分:命令实行底层事理探究-PHP (四)

针对Linux平台下,PHP命令实行函数的底层事理剖析。

本文《 命令实行底层事理探究-PHP (三) 》紧张讲述的是第三部分:针对Windows平台下,PHP命令实行函数的底层事理剖析。

PHP for Windows

针对Windows平台下:PHP命令实行函数的底层剖析。

命令实行底层剖析

针对命令实行函数的底层剖析,这里紧张采取两种手段去剖析:静态审计(静态审计内核源码)、动态审计(动态调试内核源码)。

静态审计

PHP命令实行函数有很多

systemexecpassthrushell_execproc_openpopenpcntl_execescapeshellargescapeshellcmd 、、、、

大部分命令实行函数于ext/standard/exec.c源码中实现

/ {{{ proto string exec(string command [, array &output [, int &return_value]]) Execute an external program /PHP_FUNCTION(exec){ php_exec_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);}/ }}} // {{{ proto int system(string command [, int &return_value]) Execute an external program and display output /PHP_FUNCTION(system){ php_exec_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);}/ }}} // {{{ proto void passthru(string command [, int &return_value]) Execute an external program and display raw output /PHP_FUNCTION(passthru){ php_exec_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, 3);}/ }}} // {{{ proto string shell_exec(string cmd) Execute command via shell and return complete output as string /PHP_FUNCTION(shell_exec){ FILE in; char command; size_t command_len; zend_string ret; php_stream stream; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(command, command_len) ZEND_PARSE_PARAMETERS_END();#ifdef PHP_WIN32 if ((in=VCWD_POPEN(command, "rt"))==NULL) {#else if ((in=VCWD_POPEN(command, "r"))==NULL) {#endif php_error_docref(NULL, E_WARNING, "Unable to execute '%s'", command); RETURN_FALSE; } stream = php_stream_fopen_from_pipe(in, "rb"); ret = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0); php_stream_close(stream); if (ret && ZSTR_LEN(ret) > 0) { RETVAL_STR(ret); }}/ }}} /

不雅观察上面代码部分,可以创造system、exec、passthru这三个命令实行函数调用函数一样,皆为php_exec_ex()函数,不同点只在于调用函数的第二个参数mode不同0、1、3作为标识。
而shell_exec函数则是调用VCWD_POPEN()函数去实现。

下面以system()命令实行函数实行whoami指令为例:

system('whoami');

借助源码审核对象Source Insight【导入php7.2.9源码项目】进行底层函数跟踪剖析

首先找到php中system()函数声明处:ext\standard\exec.c:263

PHP_FUNCTION(system){ php_exec_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);}

很明显system函数由php_exec_ex()函数实现,跟进同文件下找到php_exec_ex()函数实现【在Source Insight下面可以利用Ctrl+鼠标左键点击定位函数位置】:ext\standard\exec.c:209

static void php_exec_ex(INTERNAL_FUNCTION_PARAMETERS, int mode) / {{{ /{ char cmd; size_t cmd_len; zval ret_code=NULL, ret_array=NULL; int ret; ZEND_PARSE_PARAMETERS_START(1, (mode ? 2 : 3)) Z_PARAM_STRING(cmd, cmd_len) Z_PARAM_OPTIONAL if (!mode) { Z_PARAM_ZVAL_DEREF(ret_array) } Z_PARAM_ZVAL_DEREF(ret_code) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (!cmd_len) { php_error_docref(NULL, E_WARNING, "Cannot execute a blank command"); RETURN_FALSE; } if (strlen(cmd) != cmd_len) { php_error_docref(NULL, E_WARNING, "NULL byte detected. Possible attack"); RETURN_FALSE; } if (!ret_array) { ret = php_exec(mode, cmd, NULL, return_value); } else { if (Z_TYPE_P(ret_array) != IS_ARRAY) { zval_ptr_dtor(ret_array); array_init(ret_array); } else if (Z_REFCOUNT_P(ret_array) > 1) { zval_ptr_dtor(ret_array); ZVAL_ARR(ret_array, zend_array_dup(Z_ARR_P(ret_array))); } ret = php_exec(2, cmd, ret_array, return_value); } if (ret_code) { zval_ptr_dtor(ret_code); ZVAL_LONG(ret_code, ret); }}/ }}} /

阅读php_exec_ex()函数实现,会对cmd参数进行初始化处理,然后调用php_exec(mode, cmd, NULL, return_value)函数,mode为不同实行函数标识、cmd为指令参数。

跟踪php_exec()函数调用:ext\standard\exec.c:97

/ {{{ php_exec If type==0, only last line of output is returned (exec) If type==1, all lines will be printed and last lined returned (system) If type==2, all lines will be saved to given array (exec with &$array) If type==3, output will be printed binary, no lines will be saved or returned (passthru) /PHPAPI int php_exec(int type, char cmd, zval array, zval return_value){ FILE fp; char buf; size_t l = 0; int pclose_return; char b, d=NULL; php_stream stream; size_t buflen, bufl = 0;#if PHP_SIGCHILD void (sig_handler)() = NULL;#endif#if PHP_SIGCHILD sig_handler = signal (SIGCHLD, SIG_DFL);#endif#ifdef PHP_WIN32 fp = VCWD_POPEN(cmd, "rb");#else fp = VCWD_POPEN(cmd, "r");#endif if (!fp) { php_error_docref(NULL, E_WARNING, "Unable to fork [%s]", cmd); goto err; } stream = php_stream_fopen_from_pipe(fp, "rb"); buf = (char ) emalloc(EXEC_INPUT_BUF); buflen = EXEC_INPUT_BUF; if (type != 3) { b = buf; while (php_stream_get_line(stream, b, EXEC_INPUT_BUF, &bufl)) { / no new line found, let's read some more / if (b[bufl - 1] != '\n' && !php_stream_eof(stream)) { if (buflen < (bufl + (b - buf) + EXEC_INPUT_BUF)) { bufl += b - buf; buflen = bufl + EXEC_INPUT_BUF; buf = erealloc(buf, buflen); b = buf + bufl; } else { b += bufl; } continue; } else if (b != buf) { bufl += b - buf; } if (type == 1) { PHPWRITE(buf, bufl); if (php_output_get_level() < 1) { sapi_flush(); } } else if (type == 2) { / strip trailing whitespaces / l = bufl; while (l-- > 0 && isspace(((unsigned char )buf)[l])); if (l != (bufl - 1)) { bufl = l + 1; buf[bufl] = '\0'; } add_next_index_stringl(array, buf, bufl); } b = buf; } if (bufl) { / strip trailing whitespaces if we have not done so already / if ((type == 2 && buf != b) || type != 2) { l = bufl; while (l-- > 0 && isspace(((unsigned char )buf)[l])); if (l != (bufl - 1)) { bufl = l + 1; buf[bufl] = '\0'; } if (type == 2) { add_next_index_stringl(array, buf, bufl); } } / Return last line from the shell command / RETVAL_STRINGL(buf, bufl); } else { / should return NULL, but for BC we return "" / RETVAL_EMPTY_STRING(); } } else { while((bufl = php_stream_read(stream, buf, EXEC_INPUT_BUF)) > 0) { PHPWRITE(buf, bufl); } } pclose_return = php_stream_close(stream); efree(buf);done:#if PHP_SIGCHILD if (sig_handler) { signal(SIGCHLD, sig_handler); }#endif if (d) { efree(d); } return pclose_return;err: pclose_return = -1; goto done;}/ }}} /

审计int php_exec(int type, char cmd, zval array, zval return_value)函数代码,创造函数内部会首先调用VCWD_POPEN()函数去处理cmd指令【在这里不难创造该部分函数VCWD_POPEN()调用同shell_exec()可实行函数实现事理相同,也就解释system、exec、passthru、shell_exec这类命令实行函数事理相同,底层都调用了相同函数VCWD_POPEN()去实行系统指令】。

这里的VCWD_POPEN()函数调用会通过相应的平台去实行:PHP_WIN32为Windows平台、另一个为Unix平台

#ifdef PHP_WIN32 fp = VCWD_POPEN(cmd, "rb");#else fp = VCWD_POPEN(cmd, "r");#endif

进入VCWD_POPEN(cmd, "rb")函数: Zend\zend_virtual_cwd.h:269

#define VCWD_POPEN(command, type) virtual_popen(command, type)

由于VCWD_POPEN函数为virtual_popen实现,直接进入virtual_popen()函数实现:Zend\zend_virtual_cwd.c:1831

#ifdef ZEND_WIN32CWD_API FILE virtual_popen(const char command, const char type) / {{{ /{ return popen_ex(command, type, CWDG(cwd).cwd, NULL);}/ }}} /#else / Unix /CWD_API FILE virtual_popen(const char command, const char type) / {{{ /{ size_t command_length; int dir_length, extra = 0; char command_line; char ptr, dir; FILE retval; command_length = strlen(command); dir_length = CWDG(cwd).cwd_length; dir = CWDG(cwd).cwd; while (dir_length > 0) { if (dir == '\'') extra+=3; dir++; dir_length--; } dir_length = CWDG(cwd).cwd_length; dir = CWDG(cwd).cwd; ptr = command_line = (char ) emalloc(command_length + sizeof("cd '' ; ") + dir_length + extra+1+1); memcpy(ptr, "cd ", sizeof("cd ")-1); ptr += sizeof("cd ")-1; if (CWDG(cwd).cwd_length == 0) { ptr++ = DEFAULT_SLASH; } else { ptr++ = '\''; while (dir_length > 0) { switch (dir) { case '\'': ptr++ = '\''; ptr++ = '\\'; ptr++ = '\''; / fall-through / default: ptr++ = dir; } dir++; dir_length--; } ptr++ = '\''; } ptr++ = ' '; ptr++ = ';'; ptr++ = ' '; memcpy(ptr, command, command_length+1); retval = popen(command_line, type); efree(command_line); return retval;}/ }}} /#endif

不难创造,针对virtual_popen()函数实现,也存在于不同平台,这里紧张剖析Windows平台,针对Unix平台不才面PHP for Linux部分会详细讲述。

针对Windows平台,virtual_popen()函数实现非常大略,直接调用popen_ex()函数进行返回。

进入popen_ex()函数实现:TSRM\tsrm_win32.c:473

TSRM_API FILE popen_ex(const char command, const char type, const char cwd, char env){/{{{/ FILE stream = NULL; int fno, type_len, read, mode; STARTUPINFOW startup; PROCESS_INFORMATION process; SECURITY_ATTRIBUTES security; HANDLE in, out; DWORD dwCreateFlags = 0; BOOL res; process_pair proc; char cmd = NULL; wchar_t cmdw = NULL, cwdw = NULL, envw = NULL; int i; char ptype = (char )type; HANDLE thread_token = NULL; HANDLE token_user = NULL; BOOL asuser = TRUE; if (!type) { return NULL; } /The following two checks can be removed once we drop XP support / type_len = (int)strlen(type); if (type_len <1 || type_len > 2) { return NULL; } for (i=0; i < type_len; i++) { if (!(ptype == 'r' || ptype == 'w' || ptype == 'b' || ptype == 't')) { return NULL; } ptype++; } cmd = (char)malloc(strlen(command)+strlen(TWG(comspec))+sizeof(" /c ")+2); if (!cmd) { return NULL; } sprintf(cmd, "%s /c \"%s\"", TWG(comspec), command); cmdw = php_win32_cp_any_to_w(cmd); if (!cmdw) { free(cmd); return NULL; } if (cwd) { cwdw = php_win32_ioutil_any_to_w(cwd); if (!cwdw) { free(cmd); free(cmdw); return NULL; } } security.nLength = sizeof(SECURITY_ATTRIBUTES); security.bInheritHandle = TRUE; security.lpSecurityDescriptor = NULL; if (!type_len || !CreatePipe(&in, &out, &security, 2048L)) { free(cmdw); free(cwdw); free(cmd); return NULL; } memset(&startup, 0, sizeof(STARTUPINFOW)); memset(&process, 0, sizeof(PROCESS_INFORMATION)); startup.cb = sizeof(STARTUPINFOW); startup.dwFlags = STARTF_USESTDHANDLES; startup.hStdError = GetStdHandle(STD_ERROR_HANDLE); read = (type[0] == 'r') ? TRUE : FALSE; mode = ((type_len == 2) && (type[1] == 'b')) ? O_BINARY : O_TEXT; if (read) { in = dupHandle(in, FALSE); startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE); startup.hStdOutput = out; } else { out = dupHandle(out, FALSE); startup.hStdInput = in; startup.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); } dwCreateFlags = NORMAL_PRIORITY_CLASS; if (strcmp(sapi_module.name, "cli") != 0) { dwCreateFlags |= CREATE_NO_WINDOW; } / Get a token with the impersonated user. / if(OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &thread_token)) { DuplicateTokenEx(thread_token, MAXIMUM_ALLOWED, &security, SecurityImpersonation, TokenPrimary, &token_user); } else { DWORD err = GetLastError(); if (err == ERROR_NO_TOKEN) { asuser = FALSE; } } envw = php_win32_cp_env_any_to_w(env); if (envw) { dwCreateFlags |= CREATE_UNICODE_ENVIRONMENT; } else { if (env) { free(cmd); free(cmdw); free(cwdw); return NULL; } } if (asuser) { res = CreateProcessAsUserW(token_user, NULL, cmdw, &security, &security, security.bInheritHandle, dwCreateFlags, envw, cwdw, &startup, &process); CloseHandle(token_user); } else { res = CreateProcessW(NULL, cmdw, &security, &security, security.bInheritHandle, dwCreateFlags, envw, cwdw, &startup, &process); } free(cmd); free(cmdw); free(cwdw); free(envw); if (!res) { return NULL; } CloseHandle(process.hThread); proc = process_get(NULL); if (read) { fno = _open_osfhandle((tsrm_intptr_t)in, _O_RDONLY | mode); CloseHandle(out); } else { fno = _open_osfhandle((tsrm_intptr_t)out, _O_WRONLY | mode); CloseHandle(in); } stream = _fdopen(fno, type); proc->prochnd = process.hProcess; proc->stream = stream; return stream;}/}}}/

从TSRM\tsrm_win32.c文件不难创造,由virtual_popen()函数不同平台到popen_ex()函数可知,virtual_popen()函数是作为不同平台的分割点,此时的调用链已经到了仅和windows平台有联系。

接着对popen_ex()函数进行剖析,参数:command为指令参数、cwd为当前事情目录、env为环境变量信息。

为cmd变量动态分配空间:这里不得不说把cmd变量的空间分配的刚刚好

cmd = (char)malloc(strlen(command)+strlen(TWG(comspec))+sizeof(" /c ")+2);

分配空间后,为cmd变量赋值

sprintf(cmd, "%s /c \"%s\"", TWG(comspec), command);=> cmd = "cmd.exe /c whoami"

这部分其实在PHP官方手册的可实行函数中也有解释

到这里也就会创造system、exec、passthru、shell_exec这类命令实行函数底层都会调用系统终端cmd.exe来实行传入的指令参数。
那么既然会调用系统cmd,就要将cmd进程启动起来。

连续向后剖析popen_ex()函数,会找到干系Windows系统API来启动cmd.exe进程,然后由cmd进程实行指令参数(内部|外部指令)。

if (asuser) { res = CreateProcessAsUserW(token_user, NULL, cmdw, &security, &security, security.bInheritHandle, dwCreateFlags, envw, cwdw, &startup, &process); CloseHandle(token_user); } else { res = CreateProcessW(NULL, cmdw, &security, &security, security.bInheritHandle, dwCreateFlags, envw, cwdw, &startup, &process); }

在 Windows 平台上,创建进程有 WinExec,system,_spawn/_wspawn,CreateProcess,ShellExecute 等多种路子,但上述函数基本上还是由 CreateProcess Family 封装的。
在 Windows 利用 C/C++ 创建进程应该优先利用 CreateProcess,CreateProcess有三个变体,紧张是为了支持以其他权限启动进程, CreateProcess 及其变体如下:

FunctionFeatureDetailsCreateProcessW/A创建常规进程,权限继续父进程权限CreateProcessAsUserW/A利用主 Token 创建进程,子进程权限与 Token 限定同等必须开启 SE_INCREASE_QUOTA_NAMECreateProcessWithTokenW利用主 Token 创建进程,子进程权限与 Token 限定同等必须开启 SE_IMPERSONATE_NAMECreateProcessWithLogonW/A利用指定用户凭据启动进程

PS:有关Windows系统API的调用情形,一样平常编程措辞启动某个可实行程序的进程,都会调用CreateProcessW系统API,而不该用CreateProcessAsUserW系统API。
同时在cmd终端进程下,启动外部指令程序所调用的系统API一样平常为CreateProcessInternalW。

接着将进程运行的结果信息以流的形式返回,终极完成PHP命令实行函数的全体调用过程。

if (read) { fno = _open_osfhandle((tsrm_intptr_t)in, _O_RDONLY | mode); CloseHandle(out); } else { fno = _open_osfhandle((tsrm_intptr_t)out, _O_WRONLY | mode); CloseHandle(in); } stream = _fdopen(fno, type); proc->prochnd = process.hProcess; proc->stream = stream; return stream;

同理,按照上述全体审计思路,可整理出PHP常见命令实行函数在Windows平台下的底层调用链

动态审计

有了上面静态审计部分的剖析,后续进行动态调试会很方便。
这里同样以system()函数实行whoami指令为例来进行动态调试,其它函数调试事理类似。

// test.php<?phpsystem("whoami");?>

在ext/standard/exec.c:265中对system()函数实现入口处下断点,F5启动调试,运行至断点处

F11步入函数php_exec_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1)内部:ext\standard\exec.c:209

php_exec_ex()对cmd参数初始化处理后调用php_exec(mode, cmd, NULL, return_value)函数

F11步入php_exec()函数:ext\standard\exec.c:97,php_exec()函数会传入cmd指令调用VCWD_POPEN()函数

F11步入VCWD_POPEN()函数实现:

#define VCWD_POPEN(command, type) virtual_popen(command, type)Zend\zend_virtual_cwd.h:269

由于VCWD_POPEN函数为virtual_popen实现,直接进入virtual_popen()函数实现:Zend\zend_virtual_cwd.c:1831

virtual_popen()函数将cmd指令、当前事情空间等参数传给popen_ex(command, type, CWDG(cwd).cwd, NULL)函数实行返回。

F11步入popen_ex()函数实现:TSRM\tsrm_win32.c:473

跟进popen_ex()函数,对cmd进行动态分配空间及赋值

从cmd赋值的结果上来看,命令实行函数实行命令由底层调用cmd.exe来实行相应系统指令(内部|外部)。

后续调用CreateProcessW()系统API来启动cmd.exe进程,实行相应的指令即可。

查看函数之间的调用栈

如果纯挚的是想知道某个命令实行函数是否调用cmd.exe终端去实行系统指令的话,可以在php脚本里面写一个循环,然后不雅观察进程创建情形即可:大略、粗暴。

参考链接Build your own PHP on WindowsVisual Studio docsVisual Studio Code docs《PHP 7底层设计与源码实现+PHP7内核阐发》深入理解 PHP 内核WINDOWS下用VSCODE调试PHP7源代码调式PHP源码用vscode调试php源码GDB: The GNU Project DebuggerCreateProcessW function命令注入成因小谈浅谈从PHP内核层面戒备PHP WebShellProgram execution Functionslinux系统调用system calls

标签:

相关文章