When safe_mode = on and safe_mode_exec_dir is empty, [null by default]. php has a security risk during this process. In windows, exec ()/system ()/passthru () attackers can execute programs by introducing them to bypass the security mode.
Author: 80vul-B
Team: http://www.80vul.com
Date: 2009-05-27
Preface
Yesterday, milw0rm announced a very interesting vulnerability: PHP <= 5.2.9 Local Safemod Bypass Exploit (win32 ). after seeing the announcement, the author analyzed the root cause of this problem based on the php source code.
Description
In the php manual, there is a section: <functions restricted or blocked by the security mode> which provides some functions affected by the security mode, such:
Backtick operator this function is disabled in safe mode.
Shell_exec () (same function as the backticks function) this function is disabled in safe mode.
Exec () can only be executed in the directory set by safe_mode_exec_dir. For some reason, it cannot be used in the path of the executable object... Escapeshellcmd () will be applied to the parameters of this function.
System () can only be executed in the directory set by safe_mode_exec_dir. For some reason, it cannot be used in the path of the executable object... Escapeshellcmd () will be applied to the parameters of this function.
Passthru () can only be executed in the directory set by safe_mode_exec_dir. For some reason, it cannot be used in the path of the executable object... Escapeshellcmd () will be applied to the parameters of this function.
Popen () can only be executed in the directory set by safe_mode_exec_dir. For some reason, it cannot be used in the path of the executable object... Escapeshellcmd () will be applied to the parameters of this function.
Careful friends may find that in safe mode, the four functions exec ()/system ()/passthru ()/popen () are not disabled, it only needs to be executed in the safe_mode_exec_dir directory. However, when safe_mode = on and safe_mode_exec_dir is blank [empty by default], php may have security risks during this process, in windows, exec ()/system ()/passthru () can be introduced to execute the program :)
[Ps: popen () does not exist. The reason will be given later in this article.]
Three code analysis:
Take the exec () function as an example to analyze the source code:
PHP code
C ++ code
// Exec. c
PHP_FUNCTION (exec)
{
Php_exec_ex (INTERNAL_FUNCTION_PARAM_PASSTHRU, 0 );
}
// System (), passthru () function is also called php_exec_ex, but popen () is not
...
Static void php_exec_ex (INTERNAL_FUNCTION_PARAMETERS, int mode)
{
Char * cmd;
Int performance_len;
Zval * ret_code = NULL, * ret_array = NULL;
Int ret;
...
If (zend_parse_parameters (ZEND_NUM_ARGS () TSRMLS_CC, "s | z/", & cmd, & cmd_len, & ret_array, & ret_code) = FAILURE ){
RETURN_FALSE;
}
...
If (! Ret_array ){
Ret = php_exec (mode, cmd, NULL, return_value TSRMLS_CC );
...
Int php_exec (int type, char * cmd, zval * array, zval * return_value TSRMLS_DC)
{
...
If (PG (safe_mode )){
If (c = strchr (cmd ,))){
* C =;
C ++;
}
// Take the parameter section in cmd
If (strstr (cmd ,"..")){
Php_error_docref (NULL TSRMLS_CC, E_WARNING, "No .. components allowed in path ");
Goto err;
}
// Do not allow the use of... to jump to the directory. This is also the php manual to describe the processing code...
B = strrchr (cmd, PHP_DIR_SEPARATOR );
// In win, PHP_DIR_SEPARATOR is, * nix is/, which is defined in main/php. h.
// If cmd is 80 vuldir, the obtained value is dir.
Spprintf (& d, 0, "% s", PG (safe_mode_exec_dir), (B? "": "/"), (B? B: cmd), (c? "": ""), (C? C :""));
// This sentence is the key to this security risk
// If safe_mode_exec_dir is not set in php. ini, 80vuldir is processed as dir [/dir will be processed if dir is submitted directly]
// This is also the reason why "[null by default] is required when safe_mode_exec_dir is null"
If (c ){
* (C-1) =;
}
Pai_p = php_escape_shell_cmd (d );
// Php_escape_shell_cmd is called here for processing
...
# Ifdef PHP_WIN32
Fp = VCWD_POPEN (pai_p, "rb ");
# Else
Fp = VCWD_POPEN (pai_p, "r ");
# Endif
...
Char * php_escape_shell_cmd (char * str ){
Register int x, y, l;
Char * cmd;
Char * p = NULL;
TSRMLS_FETCH ();
L = strlen (str );
Cmd = safe_emalloc (2, l, 1 );
For (x = 0, y = 0; x <l; x ++ ){
// Strlen used here, so this function is not safe binary
Int mb_len = php_mblen (str + x, (l-x ));
/* Skip non-valid multibyte characters */
If (mb_len <0 ){
Continue ;&