上周三晚上,一个老客户给我发消息,说他的小站从周一开始半夜两点准时往外发流量,量还不小,CDN 账单直接翻了三倍。他自己查了一圈日志,什么都没查出来,因为那条流量根本不是从 Web 访问进来的,是从系统内部定时发出去的。我到他那台机器上一看,好家伙,宝塔面板的计划任务列表里,躺着一条他从来没建过的任务。
如果你也在运维一台带面板的服务器,或者正在考虑给站点配一台干净的环境,可以先看看这几家的服务器报价和技术说明,选机器时把带宽、防护和线路一起对比着看:数掘科技(高防防护 / 全球云服务器)
这条任务是三个月前被人写进去的。三个月里,它每隔几小时执行一次,把一个远端脚本拉下来跑,跑完就删自己。我要是没查面板,可能到服务器到期都不会知道。

做运维的人被教了一堆东西:端口要收敛、安全组只开必要端口、SSH 禁密码登录、面板入口要改。这些都对,但有一个东西经常被漏掉面板里的计划任务,也就是 crontab 那一层。
原因很简单。端口是暴露在外的,扫一下就知道;计划任务不是。系统自带的 cron、面板自己维护的一份计划任务库、还有 systemd 的 timer,加上 /etc/rc.local、init.d 下的启动脚本、bash_profile 里的一行这些东西全都不在「端口」这个视角里。攻击者拿下面板之后,最喜欢做的事就是把后门塞进这一层,因为它不占端口、不起常驻进程,日志里也只是一条普通的定时记录。
我后来复盘那个客户机器上的痕迹,发现入侵路径其实是这样的:先是面板的旧版本有一个已知漏洞被利用,拿到的是 root;然后攻击者没有急着做 SSH 爆破那种动静大的事,而是直接在面板的计划任务里写了第一条命令,下载一个挖矿相关的二进制并静默运行。整个过程在带宽上是看不出来的,因为流量很少;也不需要持续连回 C2,跑一次干完活就清理掉现场。
更麻烦的是,三个月里我那位客户自己还改过两次面板密码、升级过系统,唯独没有看过计划任务列表。他以为「我把密码改了就安全了」,但那条任务早就写死在磁盘上了,跟面板密码没关系。

我现在固定做一套检查流程,新机器上手就过一遍,交付给客户前再过一遍。顺序是这样的。
第一层,看面板的计划任务列表,对比自己的记忆。面板的好处是它把很多地方集中显示了,你要做的只是认真对一遍条目名称和执行周期。名称带一串随机字符、或者干脆是一堆 base64 编码的,基本不用细看。
第二层,直接看 crontab 本身。系统级的是 /etc/crontab 和 /etc/cron.d/ 目录,用户级的是每个用户的 /var/spool/cron/ 下对应文件。另外还有 /etc/cron.hourly、/etc/cron.daily、/etc/cron.weekly 这些目录,里面放的是可执行脚本,很多 Trojan 类的东西就伪装成普通脚本混在这里。
第三层,看 systemd 的定时器。systemctl list-timers --all 会把启用的 timer 全列出来,跟 systemctl list-unit-files --type=timer 交叉看一眼有没有没在预期里的服务。
第四层,看启动链。/etc/rc.local、/etc/init.d/、/etc/profile.d/ 下多出来的脚本,还有 root 和 www 用户的 .bashrc、.bash_profile 尾部被追加的行。这一层最隐蔽,因为平时谁会去看 shell 配置文件。
第五层是交叉验证。看进程和文件的对应关系有没有说不通的地方:某个进程在跑,但它的父进程早没了;某个文件的修改时间比系统重装还晚;netstat 或 ss 里看到一个连到陌生地址的连接,但你在 ps 里找不到对应的程序名(很可能是被改了 argv[0] 或者被直接从内存里跑的)。

找到之后不要急着删。删之前先把证据留全:任务的完整内容、执行时间、涉及的路径、还有那条任务执行出来的脚本。删完重启一次,再复查一遍我见过有人删了 crontab 里的,结果脚本在 /etc/rc.local 里还埋了一份,下次开机又活过来。
然后是改密码这一步。面板密码、SSH 密码、数据库密码、以及服务器上所有应用自己的密钥,全部换一遍。这里有个细节容易忽略:如果攻击者拿到过 root,他完全可能在 /root/.ssh/authorized_keys 里加了自己的公钥,也可能在 SSH 的 authorized_keys 之外留了别的东西,所以不只换密码,还要把 authorized_keys 逐行核对,只留你认识的那些。
最后一步是查有没有对外开的奇怪端口,以及防火墙规则里有没有多出来的放行条目。顺便提一句,如果你的机器本身就在被人用来做批量攻击或者跑挖矿,光清理自家服务器上的痕迹是不够的,入口必须堵上。我现在给客户的机器都会顺带配一层高防防护,把流量侧的脏东西先挡在门外,省得清理完没两天又被重新种上。

那件事之后我调整了一个思路:以前装完面板,我第一反应是「把该关的都关掉」,逐项去设置面板后台的安全选项,逐条去改。现在我改成反过来先决定这个面板到底需要哪些功能,用不上的直接不装、不开。
具体说这么几件事。
计划任务这个功能,如果你的服务器不需要面板定时跑脚本,那就在面板里直接关掉它。对大部分纯静态站、纯展示站来说,这个功能一年也用不上几次,但它恰好是攻击者最喜欢的落点。关掉之后,就算面板被拿下,对方也得绕一道弯去找别的持久化方式。
文件管理、在线编辑这类能直接改文件的功能,同理。很多时候我用 sftp 或者 rsync 传文件更顺手,面板的文件管理反而用得少。
还有几个我一律关掉的:默认的 FTP 服务、数据库的远程访问(数据库只允许本机连,运维需要远程连的时候走跳板机或者临时开一下)、面板自带的那个「在线申请证书」之外的所有自动任务。
面板本身也尽量只在本机访问,需要远程管理就走 SSH 隧道连到本机端口,而不是把面板端口直接暴露到公网。非要暴露的话,至少上强口令 + 双因素绑定,并且把入口限制在固定 IP 或者跳板机上。
再往前一步的思路是干脆不用面板。配置能力足够的机器,用 docker compose 或者 nginx 配置文件 + systemd 就够了,可审计性比面板强得多,因为所有配置都是明文文件、能进 git、能被 code review。我自己的机器现在全是这套,面板只在给客户托管、对方又强烈要求可视化界面的时候才装。
最后说一句我踩过的坑。出事那天我第一反应是去翻日志,翻了两个小时,什么都没翻到。因为正常业务日志根本不记系统层面的事。最后是靠一条一条比对计划任务才找到方向。
所以现在每台新机器上线,我会先跑一遍基线采集:把 crontab、systemd timer、监听端口、防火墙规则、启动脚本列表、计划任务列表全部导出存一份基线快照,放到版本控制里。以后每次排查,第一步就是跟基线做 diff,看多了什么少了什么。
这个做法很土,但它救过我两次。攻击者动过的东西,一定和基线不一样;而机器上所有「不一样」的点加在一起,通常就指向一个方向。人眼在几百条记录里找差异很难,diff 一下就出来了。
如果你现在手上有带面板的服务器,建议今晚就花十分钟,把计划任务列表和 crontab 文件各导出一份,跟你自己的记忆对一遍。多数时候你会发现没什么事;但那少数情况里的几次,足够让你记很久。