在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替代传统cronDebian 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提权这条路基本就堵死了。安全没有银弹,但把每一层都做扎实,攻击者的成本就会高到放弃。
