/*--------------- *Author:xy7 *Data:2006/11/1 *--------------- */ 如今基于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"nn"; echo "URL:"; echo htmlspecialchars(stripslashes( $a[1])); echo"nn"; 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工具等,只要你能想到:) |