在Debian系统中,cron任务如果权限配置不当,极易成为本地提权的跳板。核心问题在于:当cron以root身份运行脚本时,如果脚本文件本身对普通用户可写,攻击者只需修改脚本内容就能在下次执行时获得root权限。解决这个问题的关键是三步——锁定脚本文件权限、限制cron任务所有者、通过AppArmor或SELinux做额外隔离。下面我把每一步的具体操作、原理和验证方法全部讲透。

一、为什么cron任务会成为提权入口

Debian默认的cron守护进程会读取/etc/crontab、/etc/cron.d/目录下的文件以及各个用户的crontab。这些任务可以指定以任意用户身份运行,包括root。问题出在文件权限上。如果一个以root运行的cron脚本,其文件权限是644甚至777,那么系统上任何一个普通用户都可以编辑这个脚本,写入恶意命令。等到cron触发执行时,恶意代码就以root身份跑起来了,提权完成。

实际场景中这种漏洞非常常见。比如管理员写了一个备份脚本放在/usr/local/bin/backup.sh,设置了crontab每小时以root执行,但忘记chmod,文件权限是644。任何登录系统的低权限用户都能echo 'rm -rf /' >> /usr/local/bin/backup.sh,下一小时就灾难了。

二、第一道防线:脚本文件权限硬锁定

最基础也最有效的方法是把cron脚本的权限设为500(仅所有者可读可执行)或550(所有者和同组可读可执行),并且确保所有者是root,所属组也是root。

chown root:root /usr/local/bin/backup.sh
chmod 500 /usr/local/bin/backup.sh

这样做之后,普通用户既不能读取脚本内容,也不能修改它。如果你的cron脚本需要被多个管理员查看但不允许修改,可以用540权限,所有者root,组设为一个专门的admin组,只把信任的管理员加入这个组。

还有一个容易忽略的点:脚本调用的所有依赖文件、配置文件、临时文件目录,权限也要一并收紧。比如脚本会写日志到/var/log/backup.log,那这个日志文件如果是666权限,攻击者虽然改不了脚本,但可以通过日志注入或者符号链接攻击绕过。建议日志文件也设为600,目录设为700。

三、第二道防线:cron任务配置文件权限管控

不仅脚本本身要锁,cron的配置文件同样需要保护。/etc/crontab文件默认权限是644,但应该改成600。/etc/cron.d/目录下的每个文件也应该是600权限,所有者root。

chmod 600 /etc/crontab
chmod 600 /etc/cron.d/*
chown root:root /etc/cron.d/*

为什么要这么做?因为如果/etc/cron.d/下某个文件是644权限,普通用户可以直接在里面添加一行新的cron任务,比如:

* * * * * root /tmp/evil.sh

这样攻击者就不需要动已有的脚本,直接新建一个任务就行。所以cron.d目录下所有文件必须600,目录本身700。

另外,要定期检查是否有非root用户的crontab在运行。用以下命令查看:

for user in $(cut -f1 -d: /etc/passwd); do echo "=== $user ==="; crontab -u $user -l 2>/dev/null; done

如果发现某个低权限用户有cron任务在跑,而且任务内容可疑,立刻处理。普通用户的cron任务虽然不会直接提权到root,但可以作为跳板配合其他漏洞使用。

四、第三道防线:用AppArmor限制cron脚本行为

Debian默认启用AppArmor,这是一个非常好的内核级安全模块,可以对程序的文件访问、网络访问、能力(capabilities)做细粒度限制。我们可以给cron任务写一个专属的AppArmor配置文件。

首先创建配置文件:

sudo nano /etc/apparmor.d/usr.local.bin.backup

写入以下内容:

#include <tunables/global>

/usr/local/bin/backup.sh flags=(complain) {
  #include <abstractions/base>
  #include <abstractions/bash>

  # 只允许读取必要的配置文件
  /etc/backup.conf r,
  # 只允许写入指定的备份目录
  /backup/ rw,
  # 禁止访问其他敏感路径
  deny /etc/shadow r,
  deny /etc/passwd r,
  deny /root/ rw,
  deny /tmp/ rw,
  # 禁止执行其他程序
  deny /usr/bin/sudo x,
  deny /usr/bin/bash ix,
  # 禁止网络访问
  deny network raw,
  deny network packet,
}

这个配置的意思是:backup.sh只能读取/etc/backup.conf,只能在/backup/目录下读写,不能碰shadow文件、不能执行sudo和bash、不能联网。即使脚本被篡改了,攻击者能做的事也非常有限。

加载配置:

sudo apparmor_parser -r /etc/apparmor.d/usr.local.bin.backup

如果你想先测试而不是直接强制执行,把flags=(complain)改成flags=(complain)保持不变即可,complain模式只会记录违规但不阻止。确认没问题后再改成enforce模式。

五、第四道防线:使用systemd timer替代传统cron

Debian 10之后systemd已经完全取代了cron的大部分功能。systemd timer有一个天然优势:它的unit文件权限管控比crontab更严格,而且可以通过沙箱选项直接限制权限。

创建一个timer替代cron任务:

sudo nano /etc/systemd/system/backup.service
[Unit]
Description=Backup Service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
User=root
Group=root

# 沙箱限制
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/backup
ReadOnlyPaths=/etc/backup.conf
CapabilityBoundingSet=

[Install]
WantedBy=multi-user.target
sudo nano /etc/systemd/system/backup.timer
[Unit]
Description=Run Backup Hourly

[Timer]
OnCalendar=hourly
Persistent=true

[Install]
WantedBy=timers.target

启用timer:

sudo systemctl enable --now backup.timer

这里的关键参数是NoNewPrivileges=true(禁止提权)、ProtectSystem=strict(挂载根文件系统为只读)、PrivateTmp=true(独立的/tmp空间)。即使脚本被攻击者改了,在这种沙箱里也几乎做不了什么坏事。

六、定期审计:用脚本自动检查cron安全状态

手动检查容易遗漏,建议写一个审计脚本定期跑。下面是一个实用的检查脚本:

#!/bin/bash
# cron_security_audit.sh

echo "=== Cron Security Audit Report ==="
echo "Date: $(date)"
echo ""

echo "[1] Checking /etc/crontab permissions"
ls -la /etc/crontab
if [ $(stat -c %a /etc/crontab) != "600" ]; then
  echo "WARNING: /etc/crontab is not 600!"
fi

echo ""
echo "[2] Checking /etc/cron.d/ permissions"
for f in /etc/cron.d/*; do
  perm=$(stat -c %a "$f")
  owner=$(stat -c %U:%G "$f")
  echo "$f - $perm - $owner"
  if [ "$perm" != "600" ]; then
    echo "WARNING: $f is not 600!"
  fi
done

echo ""
echo "[3] Checking for writable cron scripts"
find /etc/cron.d /usr/local/bin /usr/local/sbin -type f -perm -002 -exec ls -la {} \; 2>/dev/null

echo ""
echo "[4] Checking for suspicious cron entries"
grep -r "curl\|wget\|nc\|bash -i\|/dev/tcp" /etc/cron.d/ /var/spool/cron/ 2>/dev/null

echo ""
echo "=== End of Report ==="

把这个脚本放到/usr/local/bin/,设为每天执行一次,输出结果可以通过邮件发给管理员。这样就能持续监控cron安全状态,一旦有异常立刻发现。

七、常见误区和额外建议

很多人以为只要脚本权限设对了就万事大吉,但忽略了几个细节。第一,符号链接攻击:如果脚本路径是一个符号链接,攻击者可以把链接指向恶意文件,即使原文件权限是500也没用。解决办法是在AppArmor或systemd配置中禁用符号链接跟随,或者直接用绝对路径且不使用链接。

第二,PATH环境变量问题。cron执行时的PATH非常精简,通常只有/usr/bin:/bin。如果脚本里调用了没有绝对路径的命令,攻击者可以在用户目录下放一个同名恶意程序,利用PATH注入。所以脚本里所有命令都要用绝对路径,比如用/usr/bin/tar而不是tar。

第三,不要在cron脚本里硬编码密码或密钥。如果脚本需要访问数据库或远程服务,应该用环境变量或者专门的密钥管理文件,且这些文件权限同样要锁到600。

第四,Debian的/etc/crontab里有一个MAILTO变量,默认会把cron输出发邮件。如果脚本出错输出了敏感信息,这些邮件可能被其他用户读取。建议把MAILTO设为一个专用的安全邮箱,或者直接禁用邮件输出。

八、总结:多层防御才是正道

防止cron提权不是单一措施能解决的,必须分层。文件权限是基础,AppArmor或systemd沙箱是加固,定期审计是持续保障。在Debian系统上,优先推荐用systemd timer替代传统cron,因为它天然支持沙箱和更细的权限控制。把这套组合拳打好,cron提权这条路基本就堵死了。安全没有银弹,但把每一层都做扎实,攻击者的成本就会高到放弃。