拿到访问权限只是一半的活儿;另一半是别把它丢掉。正因如此,几乎每一个自动化的恶意程序都会先安排好自己的再次启动:重启之后、文件被删之后、密码被改之后。而那句耳熟的话也正由此而来——「清理干净了,两天后它又回来了」。
做这种安排的地方并不多,而且全部都能在几分钟内查完。下面是按顺序排的完整一圈。
1. SSH 密钥
最简单的一条回来的路:authorized_keys 里的一行能挺过改密码、系统更新和重启。
sudo find /home /root -name authorized_keys -exec ls -l {} \; -exec cat {} \;
每一行都是某人的长期访问权限。如果说不出是谁的,就当成别人的。也请留意行尾的注释:那是任意文本,跟你的名字对得上并不能证明任何事。
2. 所有用户的 cron 任务
不是只查自己的 crontab:
for u in $(cut -f1 -d: /etc/passwd); do echo "== $u"; sudo crontab -u "$u" -l 2>/dev/null; done
sudo ls -la /etc/cron.d/ /etc/cron.hourly/ /etc/cron.daily/
sudo cat /etc/crontab
值得留意的迹象:@reboot 开头的行、又长又看不懂的命令、被管进 shell 的 curl 或 wget 调用,以及任何从 /tmp、/dev/shm 或 /var/tmp 运行的东西。没有任何标准的东西会从这些目录里跑。
3. systemd 的计时器与服务
cron 的现代替代品,而它被检查的次数明显要少得多:
systemctl list-timers --all
systemctl list-units --type=service --state=running
sudo ls -la /etc/systemd/system/ /run/systemd/system/
另外单独还有用户级服务,它们不需要 root 权限就能跑,而且不出现在通用列表里:
systemctl --user list-units --type=service
sudo ls -la /home/*/.config/systemd/user/
还有一个细节:为某个用户打开的 lingering(loginctl enable-linger)会让他的服务在没有活动会话时也能运行。用 loginctl list-users 检查它。
4. shell 的启动文件
追加到 shell 启动文件末尾的代码,每次登录都会执行:
sudo tail -5 /root/.bashrc /root/.profile /home/*/.bashrc /home/*/.profile
sudo ls -la /etc/profile.d/
请正好看文件的末尾——正是在那里追加,才不会在匆匆一瞥时映入眼帘。
5. ld.so.preload
这个文件会迫使系统把某个指定的库加载进每一个运行中的进程。而在正常情况下它从来不会被创建:
ls -l /etc/ld.so.preload
它的存在实际上是严重失陷的明确信号,而这样的库通常会把文件、进程和网络连接一并藏起来。而在那之后,你在这台机器上看到的一切都不再值得信任。
6. 包管理器的 hook
一个较少被想起的途径:apt 可以在对软件包的每次操作之前和之后执行命令。
sudo ls -la /etc/apt/apt.conf.d/
sudo grep -r 'DPkg::Pre-Invoke\|DPkg::Post-Invoke\|APT::Update' /etc/apt/apt.conf.d/
这样的 hook 会在每次安装更新时执行——也就是定期地、并且以 root 身份。
7. Ubuntu 上的每日提示
目录 /etc/update-motd.d/ 里放着可执行脚本,每次 SSH 登录时都会运行并生成欢迎文本。而这个地方之所以方便,恰恰因为它看起来就是系统的一部分:
sudo ls -la /etc/update-motd.d/
8. 延迟的 at 任务
sudo atq
sudo ls -la /var/spool/cron/atjobs/ 2>/dev/null
一个老旧且很少使用的机制,也正因如此被检查得最少。
如果找到了什么该怎么办
第一反应是把找到的东西立刻删掉。而这是个错误:随之消失的还有它是怎么到那里去的这条信息,而没有那个问题的答案,一切都会重演。
- 保留那个文件或任务的副本,以及它的修改时间。
- 用这个时间去 Web 服务器日志和
auth.log里看看同一分钟发生了什么。入口通常就在那儿。 - 请把清单里全部八个地方都查一遍,而不是只查发现了东西的那一个。后门几乎从来不会只留一份。
- 然后才清理,并把漏洞本身堵上。
知道正常是什么样
这项检查真正的难点不是命令,而是:如果你不记得清单原先是什么样,那么任务列表里一行陌生的记录看起来一点也不可疑。而在一台别人配置过的、或者一年前配置好的服务器上,要分辨自己的和别人的几乎不可能。
由此得出一个实际结论:趁服务器还好好的,今天就取一份「现状」快照是有意义的。一台健康机器上的 cron 任务、计时器、密钥和服务清单,正是日后拿来比对一切的基准。而当它被汇总到一处、带着日期并受到保护时,它会把找后门这件事从几小时的功课变成两份清单的对比。它是什么样子,见下面的演示页面。