2024NCTF

艰难的比赛,来跟着官方wp复现学习一下

sqlmap-master

首先查看题目,给了附件和环境。

首先打开环境,是一个类似于sqlmap的一个界面。

附件当中给了源代码,我们查看一下。

我们发现其中有一个很明显的subprocess.Popen()函数

(1)subprocess.Popen:
(subprocess.popen是python标准库中的一个模块,用于启动和管理子进程。它可以执行外部命令并捕获其输出。)

以下是subprocess.Popen的典型用法:

import subprocess

# 启动一个子进程,执行命令 "ls -l"
process = subprocess.Popen(
    ['ls', '-l'],                # 命令及其参数
    stdout=subprocess.PIPE,      # 捕获标准输出
    stderr=subprocess.STDOUT     # 将错误输出合并到标准输出
)

# 读取命令的输出
output, _ = process.communicate()
print(output.decode())

args:要执行的命令及其参数。可以是一个字符串列表(如[‘ls’,’-l’])或者单个字符串(但需要设置shell=True).

stdout和stderr:分别指定标准输出和错误输出的处理方式。常见的值是subprocess.PIPE,表示捕获输出。

shell:是否通过系统 shell 执行命令。如果为 True,可以直接传递完整的命令字符串;如果为 False,需要传递一个参数列表。

如果 shell=True,则存在命令注入风险,因为用户输入可能会被直接拼接到 shell 命令中。
如果 shell=False,则相对安全,但仍需确保用户输入的参数经过严格校验。

学习了一下,那么回到这个题目,我们看到了设置了shell=Flase,导致我们无法利用正常的命令注入

但是我们可以看到我们可以控制sqlmap的参数,即参数注入。
结合GTFOBins:

Shell
它可用于通过生成交互式系统shell来摆脱受限环境

sqlmap -u 127.0.0.1 --eval="import os; os.system('/bin/sh')"

Sudo

如果二进制文件被允许以超级用户身份运行sudo,它不会降低提升的权限,并且可能用于访问文件系统,升级或维护特权访问。

sudo sqlmap -u 127.0.0.1 --eval="import os; os.system('/bin/sh')")

通过–eval参数可以执行Python代码,然而因为上面command.split()默认是按空格分隔的,所以需要一些技巧绕过

注意这⾥参数的值不需要加上单双引号, 因为上⾯已经设置了 shell=False , 如果加上去反⽽代表的是 “eval ⼀个 Python 字符串”

解释:

1. command.split() 的作用

在后端代码中,command.split() 是用来将用户输入的字符串按空格分割成一个列表的。例如:

command = f'sqlmap -u {url} --batch --flush-session'

如果用户输入的 url 是:

127.0.0.1:8000 --eval import('os').system('env')

那么 command.split() 将会把整个命令分割成以下列表:

[‘sqlmap’, ‘-u’, ‘127.0.0.1:8000’, ‘–eval’, ‘import('os').system('env')’, ‘–batch’, ‘–flush-session’]

注意点:

–eval 后面的内容(即 import(‘os’).system(‘env’))会被当作一个整体参数传递给 sqlmap。
这是因为 split() 默认以空格为分隔符,并且不会对内容进行额外处理。

  1. 为什么需要“一些小技巧来绕过”?

由于 command.split() 按空格分割,恶意输入中的空格可能会导致参数解析错误。例如:

127.0.0.1:8000 --eval import('os').system('whoami')

这里的 –eval 参数值包含了空格(’os’ 和 ‘whoami’ 中的单引号和括号),这可能会导致 split() 错误地将参数分割成多个部分,从而破坏命令结构。

解决方法:

为了避免这种情况,我们需要确保 –eval 参数的值是一个完整的 Python 表达式,且不包含多余的空格或特殊字符。例如:

127.0.0.1:8000 --eval import('os').system('env')

这里的关键是:

使用 import 动态导入模块,避免直接引用模块名。确保整个表达式没有多余的空格或换行。

  1. 为什么不需要加引号?

在 Python 的 subprocess.Popen 中,shell=False 是默认设置。这意味着命令参数会直接传递给底层的操作系统调用,而不会经过 shell 解释。

(1) 如果加上引号会发生什么?

假设我们在 –eval 参数值上加上引号,例如:

127.0.0.1:8000 --eval "import('os').system('env')"

后端代码会将其分割成以下列表:

['sqlmap', '-u', '127.0.0.1:8000', '--eval', '"__import__(\'os\').system(\'env\')"', '--batch', '--flush-session']

此时,–eval 的值变成了一个带有双引号的字符串:

"import('os').system('env')"

这会导致 sqlmap 将整个字符串当作一个普通的 Python 字符串来执行,而不是实际运行其中的代码。

(2) 不加引号的正确行为

如果不加引号,–eval 参数值会直接作为 Python 代码传递给 sqlmap,例如:

127.0.0.1:8000 --eval import('os').system('env')

后端代码会将其分割成以下列表:

['sqlmap', '-u', '127.0.0.1:8000', '--eval', '__import__(\'os\').system(\'env\')', '--batch', '--flush-session']

此时,–eval 的值是一个合法的 Python 表达式,sqlmap 会在运行时执行它。

最终 payload:

127.0.0.1:8000 --eval import('os').system('env')

解释:

  1. env 命令

env 是一个 Unix/Linux 系统中的内置命令,用于显示当前环境变量或在特定环境中运行程序。

功能

查看环境变量:当单独执行 env 时,它会列出当前 shell 中的所有环境变量。例如:

$ env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOME=/root USER=root

运行程序:env 还可以用来在修改环境变量的情况下运行指定的程序。例如:

$ env VAR=value myprogram

上述命令会在 VAR=value 的环境下运行 myprogram。

用途:

在渗透测试中,env 常被用来快速获取目标系统的环境信息(如路径、用户权限等),这些信息可能对进一步攻击有帮助。

传入我们的playload之后就能得到flag了

总结:

  1. 漏洞原理

后端使用 subprocess.Popen 执行 sqlmap 命令。

虽然设置了 shell=False,但用户可以通过控制 url 参数来注入额外的参数(如 –eval)。

–eval 是 sqlmap 提供的一个合法参数,用于在每次 HTTP 请求之前执行指定的 Python 代码。

结合 GTFOBins 中的技巧,可以通过 import(‘os’).system() 执行任意系统命令。

  1. 构造 Payload

最终的 Payload 是:

127.0.0.1:8000 –eval import(‘os’).system(‘env’)

解释

127.0.0.1:8000 是一个合法的目标 URL(可以替换为其他有效的 URL)。

–eval 是 sqlmap 的参数,允许我们注入 Python 代码。
__
import__(‘os’).system(‘env’) 是 Python 的动态导入机制,调用 os.system 执行系统命令 env。

后端执行的命令

后端会将输入拼接到 sqlmap 命令中,最终执行如下命令:

sqlmap -u 127.0.0.1:8000 –eval import(‘os’).system(‘env’) –batch –flush-session

由于 –eval 参数的存在,sqlmap 会在运行时执行我们注入的 Python 代码,从而触发命令执行。

ez_dash&&ez_dash_revenge

首先查看题目,他提供了一个源代码和环境

打开环境进行查看,发现是一个404错误的界面

ez_dash

首先我们先看这个题

那么我们查看一下源代码

代码分析

这段代码实现了一个基于 bottle 的 Web 服务,允许用户通过 HTTP 请求动态设置对象的值以及渲染模板。

这段代码重要的有一个函数和两个路由

setval函数

用于设置全局变量的属性。

接受三个参数:name(全局变量名)、path(属性路径)、value(要设置的值)。

使用了 pydash.set_ 来动态设置对象的嵌套属性。

包含了一些限制条件:

禁止使用双下划线(__)开头的名称。

禁止访问某些特定的内置对象(如 builtins 和 bottle)。

禁止访问某些特定的属性路径(如 class__、__dict 等)。

/setValue路由

提供一个 POST 接口,允许用户通过 HTTP 请求动态设置全局变量的属性。

对输入进行了长度限制(name 长度不超过 6,path 长度不超过 32)。

返回 “yes” 或 “no” 表示操作是否成功。

/render路由

提供一个 GET 接口,允许用户通过 HTTP 请求渲染指定路径的模板。

对路径进行了长度限制(不超过 10 个字符)。

题目

这个题目预期解是bottle服务+pydash的原型链污染,然后可以通过render渲染模板。

(原型链污染(Prototype Pollution) 是一种安全漏洞,它发生在 JavaScript 或 Python 等支持对象原型的语言中。这种漏洞允许攻击者通过修改对象的原型来注入或篡改属性,从而影响程序的行为。在 Python 中,这种情况通常出现在使用某些库(如 pydash)时,如果这些库没有正确处理用户输入。

Pydash 与原型链污染

pydash 是一个功能强大的工具库,提供了许多实用函数用于操作数据结构。然而,由于其灵活性和对动态对象的支持,如果使用不当,可能会引入原型链污染的风险。

原型链污染示例
假设我们有一个简单的 Python 对象,并且使用 pydash.set_ 方法来设置嵌套属性:

import pydash

obj = {}
pydash.set_(obj, ‘proto.polluted’, ‘true’)

在这个例子中,proto 是 JavaScript 中的对象原型链的关键字,但在 Python 中并没有直接对应的机制。不过,在一些情况下(例如在 Node.js 环境下),这会导致所有新创建的对象都包含 polluted 属性,因为 proto 影响了对象的原型链。

尽管 Python 没有直接的 proto 概念,但类似的问题可能出现在其他上下文中,特别是当 Python 应用与前端代码交互或使用某些特定库时。)

但是这个题对/render的过滤设置的不够严谨,bottle的模板渲染功能很强大,不仅能够接受{{}}/b>而且还能够接受%引入一行python代码:

因此我们可以直接往render路由打,payload:

% eval("import('os').popen('id>1')")

(render能够渲染文件,因此将文件写入再用·render渲染即可)

过滤了.,但是题目用的是eval()函数,因此我们可以用chr函数进行绕过

?path=% eval("import('os')"+chr(46)+"popen('id>1')")

注意发送的时候+要进行url编码,不然会以为是空格进行报错


2024NCTF
https://sakula33.github.io/2025/03/25/2024NCTF/
作者
sakula33
发布于
2025年3月25日
许可协议