Linux 里的权限回答的是「谁可以读这个文件」。AppArmor 回答的是另一个问题:「这个程序究竟被允许做什么」。差别在被入侵时才显现出来。一个 web shell 以 Web 服务器用户的身份运行,并继承了它的全部权限——也就是对半个系统的读取权。AppArmor 的配置文件把进程限制在它工作所需的那份清单里,于是读取 /etc/shadow 或启动 /bin/bash 的尝试,无论用户权限如何都会以被拒绝告终。
已经开启了什么
sudo aa-status
输出回答三个问题:加载了多少份配置文件,其中多少处于 enforce 和 complain 模式,以及哪些进程完全没有配置文件在跑。在一台典型的 Ubuntu 上会有几十份配置文件,但几乎全是 man、tcpdump 之类辅助程序的。主要服务——nginx、Apache、PHP-FPM——通常不在受限清单里。
要分清的三种模式:
- enforce——规则被执行,多余的一切都被拒绝;
- complain——违规只记进日志,什么都不拦。这是用来调校的模式;
- unconfined——没有配置文件,程序毫无限制地运行。
aa-status 输出里最有用的是最后一部分:那些在网络上监听、却不受任何东西约束的进程。就该从那份清单开始。
工具
sudo apt install apparmor-utils
没有这个包,aa-complain、aa-enforce 和 aa-logprof 这几条命令在系统里就不存在,尽管 AppArmor 本身在运行。
怎样开启一份配置而不让服务停摆
顺序是根本性的。一份直接切到 enforce 的配置文件极有可能拒绝掉服务所需要的某样东西,于是服务停止工作——通常不是立刻,而是在某个不常见的操作上,比如上传文件或者发邮件。
正确的顺序:
sudo aa-complain /etc/apparmor.d/usr.sbin.nginx
在这个模式下工作一周,把每一种不常见的场景都覆盖到:做备份、更新、上传大文件。然后把积累下来的东西过一遍:
sudo aa-logprof
这条命令会逐条走过被记录的违规,并针对每一条询问是否放行。这里需要专注:只放行真正属于该服务工作范围的东西,而不是眼前出现的一切——否则你会得到一份放行一切的配置文件,意义也就没了。
然后才是:
sudo aa-enforce /etc/apparmor.d/usr.sbin.nginx
在哪里看被拒绝的记录
所有违规都会进到内核日志:
sudo journalctl -k | grep -i apparmor
sudo grep 'apparmor="DENIED"' /var/log/audit/audit.log
第二个文件在装了 auditd 时才有——而有了它,记录会详细得多。一条拒绝记录会显示配置文件名、被请求的操作以及被访问的路径;这足以判断是该修配置文件,还是该庆幸它生效了。
有一个症状值得记住:服务表现得很奇怪——读不了配置、往目录里写不进去、打不开套接字——而它自己的日志里是「permission denied」,可权限明明是对的。这几乎总是 AppArmor,而第一件该做的事就是去看内核日志。
这是实用措施,不是理论措施
给 PHP-FPM 或 nginx 加一份配置文件,其回报是很具体的。当进程被约束住时,一个已经进到站点里的 web shell 既读不了系统文件,也启动不了 shell,更没法在被允许的目录之外写任何东西。一个被黑的站点仍然只是一个被黑的站点,而不会变成对整台机器的控制权——而正是这一步跨越造成了绝大部分损失。
合理的推行顺序:先给朝向互联网的东西加配置(Web 服务器、PHP-FPM),然后是数据库,之后按需要来。不必一次全开,而且也不存在通用的现成规则集:配置文件取决于你的文件放在哪里。
要定期检查什么
配置文件回落到 unconfined 的次数比人们以为的要多:一次包更新可能替换掉配置文件,手动排障会把服务落在 complain 里,而新服务干脆就没有配置。有两个数字有用:有多少份配置处于 enforce,以及有多少个朝向网络的进程仍是 unconfined。其中任何一个的变化都值得知道。汇总在一个页面上是什么样子,见下面的演示。