Linux 基础体系 · 第 8/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。
Linux 主机安全加固:最小化、SELinux/AppArmor、审计与漏洞治理
Linux 主机安全加固不是“关闭几个服务、安装一个扫描器”这么简单。它要解决的是一条完整的攻击链:
- 攻击者如何到达主机;
- 到达后能调用哪些服务和程序;
- 服务进程能读取、修改或执行哪些对象;
- 异常行为能否被记录和关联;
- 已知漏洞能否及时修复;
- 加固失败或误配置后,能否验证和恢复。
本文以现代主流发行版为范围:RHEL、Fedora、Rocky Linux、AlmaLinux 通常以 SELinux 为主,Ubuntu、Debian 通常以 AppArmor 为主;具体默认策略、服务名称、包管理命令和工具版本可能不同。下文会明确区分内核机制、常见实现和运维建议。
一、先建立安全模型:最小化不是“越少越好”
1.1 攻击面、权限面和可观测性
攻击面是攻击者能够交互的入口集合,包括:
- 监听中的网络端口;
- 对外提供的 HTTP、SSH、数据库等服务;
- 本地 Unix socket;
- SUID/SGID 程序;
- 可执行文件、脚本解释器和动态库;
- 内核接口、设备节点和容器运行时;
- 已安装但未使用的软件及其自动启动单元。
权限面是某个主体成功执行后可以影响的资源集合。Linux 中至少有三层相关机制:
- DAC(Discretionary Access Control,任意访问控制)
传统的 UID、GID、mode、ACL。文件所有者或具有足够权限的主体可以改变权限。 - Capabilities
将传统 root 的部分能力拆分,例如CAP_NET_BIND_SERVICE允许绑定低于 1024 的端口,CAP_DAC_OVERRIDE可绕过部分传统文件权限检查。 - MAC(Mandatory Access Control,强制访问控制)
SELinux 或 AppArmor 根据系统策略限制访问。主体自身通常不能随意放宽这类限制。
一次访问能否成功,不能只看文件的 chmod。可以用下面的抽象条件理解:
其中:
DAC:UID/GID、mode、ACL 检查是否通过;MAC:SELinux 或 AppArmor 是否允许;Capability:所需内核能力是否存在;ObjectState:文件系统只读、挂载选项、设备状态等是否允许。
这个公式表达的是“多个门同时通过”。例如:
- 文件 mode 允许,但 SELinux 拒绝,访问仍失败;
- SELinux 允许,但传统 Unix 权限拒绝,访问仍失败;
- 进程是 root,通常可绕过部分 DAC,但不等于绕过 SELinux;
- 进程缺少
CAP_NET_BIND_SERVICE,即使其他条件满足,也可能无法绑定低端口。
这也是为什么只执行 chmod 777 经常不能解决生产问题,反而扩大了权限面。
1.2 最小化的三个层次
最小化应分层实施,而不是只删除软件包。
第一层:最小化网络暴露
先看实际监听状态,而不是只看“安装了什么”:
sudo ss -lntup
sudo ss -lx
典型输出可能是:
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=842,fd=3))
LISTEN 0 4096 127.0.0.1:6379 0.0.0.0:* users:(("redis-server",pid=910,fd=6))
这里 0.0.0.0:22 表示监听所有 IPv4 地址,127.0.0.1:6379 只接受本机连接。两者的风险不同:即使 Redis 本身没有认证漏洞,错误地监听公网也会扩大攻击面。
随后确认防火墙实际规则。现代发行版可能使用 nftables、firewalld 或 ufw,不能只根据某个命令的输出推断内核最终规则:
sudo nft list ruleset
若使用 firewalld:
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --list-all
防火墙通常只能限制网络路径,不能替代服务自身认证和 MAC。例如允许 443 端口并不代表 Web 应用安全;防火墙也不能阻止同机恶意进程访问一个没有正确权限保护的 Unix socket。
第二层:最小化服务和软件包
查看 systemd 单元:
systemctl list-unit-files --type=service --state=enabled
systemctl --type=service --state=running
发现不需要的服务后,先确认依赖和用途:
systemctl status example.service
systemctl list-dependencies --reverse example.service
停止、禁用和屏蔽含义不同:
sudo systemctl stop example.service
sudo systemctl disable example.service
sudo systemctl mask example.service
stop:停止当前实例,重启后可能再次启动;disable:取消开机自动启动,但仍可能被手动或依赖拉起;mask:把单元链接到/dev/null,阻止常规启动请求。
生产环境不要把 mask 当作普通禁用手段。错误屏蔽可能破坏升级、监控或依赖服务。应先记录原状态,验证业务,再决定是否持久化。
包级别也要确认反向依赖:
在 RPM 系发行版中:
rpm -qf /usr/sbin/example
rpm -q --whatrequires example-package
在 Debian 系发行版中:
dpkg -S /usr/sbin/example
apt-cache rdepends example-package
删除一个软件包可能连带删除配置、库或元包。对于生产主机,更稳妥的步骤是:
- 记录包清单和服务状态;
- 在同版本测试环境验证依赖;
- 先停止服务并观察业务;
- 删除或卸载;
- 重新扫描监听端口、启动单元和应用健康状态;
- 保留可回滚的包版本或镜像。
第三层:最小化进程权限
即使服务必须存在,也不应默认以 root 运行。systemd 服务可通过以下方向减少权限:
[Service]
User=app
Group=app
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=full
ProtectHome=read-only
这些选项不是所有程序都兼容:
User、Group改变进程身份;NoNewPrivileges=yes阻止进程及其子进程通过execve获得新的特权,例如 SUID 提权;PrivateTmp=yes提供独立的临时目录视图;ProtectSystem=full将部分系统目录以只读方式暴露;ProtectHome=read-only限制对用户家目录的写入。
不要直接编辑发行版提供的 unit 文件,通常应使用 drop-in:
sudo systemctl edit example.service
保存后:
sudo systemctl daemon-reload
sudo systemctl restart example.service
sudo systemctl status example.service
验证不能只看服务是否“active”。还要检查:
ps -o pid,user,group,comm -C example
sudo systemctl show example.service \
-p User -p Group -p NoNewPrivileges -p ProtectSystem -p ProtectHome
如果服务启动失败,先查看:
journalctl -u example.service -b --no-pager
进程沙箱和 SELinux/AppArmor 是不同层次:systemd 约束进程的命名空间、挂载视图和特权;MAC 约束主体对对象的安全访问。二者可以叠加,但也可能因路径、临时目录、动态生成文件等差异造成兼容问题。
二、SELinux:基于标签和策略的强制访问控制
2.1 SELinux 的基本对象模型
SELinux 将访问描述为:
例如 Web 服务读取网页文件可以表示为:
subject: system_u:system_r:httpd_t:s0
object: system_u:object_r:httpd_sys_content_t:s0
class: file
permission: read
其中:
- subject 是进程,通常具有 SELinux domain,例如
httpd_t; - object 是文件、目录、端口、socket 等资源;
- class 是对象类别,如
file、dir、tcp_socket; - permission 是具体操作,如
read、write、execute。
SELinux 规则不是简单的“某个用户能否访问某路径”。路径只是查找对象的方式,策略主要依据对象的安全上下文和进程 domain 判断。
查看当前状态:
getenforce
sestatus
常见状态:
Enforcing:策略生效,拒绝访问并记录相关审计事件;Permissive:不实际阻断访问,但记录本应拒绝的事件;Disabled:内核不加载 SELinux。切换到启用状态通常涉及重新标记文件系统,不能当作普通运行时开关。
Permissive 适合诊断和迁移,不应被误认为“安全但只记录”。因为此时被策略拒绝的操作仍然成功,应用可能已经以不受约束的方式运行。
2.2 DAC 与 SELinux 的访问顺序
假设 Web 进程读取 /srv/www/index.html:
- 内核执行路径解析;
- 检查目录和文件的 DAC 权限、ACL;
- 检查 SELinux domain
httpd_t是否有权读取对象标签; - 检查文件系统、挂载和其他内核约束;
- 允许或拒绝;
- 若涉及 SELinux 拒绝,写入 AVC 审计事件。
因此下面两种情况都可能失败:
-rw-r--r-- 1 root root ... /srv/www/index.html
如果 Web 进程以非 root 身份运行,DAC 可能允许读取;但如果文件标签是普通用户文件类型,SELinux 仍可能拒绝。
反过来,即使标签正确:
-rw------- 1 root root ... /srv/www/index.html
SELinux 允许也不能替代 DAC,Web 进程仍可能因传统权限不足而失败。
2.3 文件上下文:临时修改与持久配置
查看上下文:
ls -lZ /srv/www/index.html
ps -eZ | grep httpd
常见 Web 内容类型:
httpd_sys_content_t:允许 Web 服务读取;httpd_sys_rw_content_t:允许特定 Web 服务写入,但扩大写权限,不能随意使用。
临时修改:
sudo chcon -t httpd_sys_content_t /srv/www/index.html
chcon 直接改变当前标签,但不一定被文件上下文数据库记住。文件重新创建、恢复标签或某些 relabel 操作后可能丢失,因此它适合实验,不适合作为持久配置。
持久配置:
sudo semanage fcontext -a -t httpd_sys_content_t '/srv/www(/.*)?'
sudo restorecon -Rv /srv/www
这里分两步:
semanage fcontext把正则路径与目标类型写入持久映射;restorecon根据映射实际设置文件标签。
验证:
matchpathcon /srv/www/index.html
ls -lZ /srv/www/index.html
matchpathcon 显示策略期望的上下文,ls -Z 显示当前实际上下文。两者不一致通常意味着没有执行 restorecon,或者存在更具体的规则覆盖。
2.4 端口标签和布尔开关
SELinux 不只标记文件,也可以标记网络端口。服务尝试监听非默认端口时,不能只修改防火墙:
sudo semanage port -l | grep http_port_t
sudo semanage port -a -t http_port_t -p tcp 8080
如果端口已经存在于其他类型,应使用 -m 修改,而不是 -a:
sudo semanage port -m -t http_port_t -p tcp 8080
具体是否允许某服务绑定该类型,还取决于策略。
布尔值是发行版策略暴露的可配置开关。例如:
getsebool -a | grep httpd
sudo setsebool -P httpd_can_network_connect on
- 不带
-P:只改变当前运行状态; - 带
-P:更新持久策略,可能需要较长时间并修改策略存储。
开启布尔值前要确认它放宽了什么。httpd_can_network_connect 可能允许 Web 服务主动连接更多网络目标;如果应用只需连接数据库,最好再通过网络分段、防火墙和应用配置限制目标,而不是把“所有网络连接能力”视为无成本开关。
2.5 AVC 拒绝的诊断流程
看到应用报“Permission denied”时,不要直接执行 audit2allow。完整流程如下:
第一步:确认是否为 SELinux 拒绝
sudo ausearch -m AVC -ts recent
或:
sudo journalctl -k -g 'avc:.*denied'
典型事件会包含:
avc: denied { read } for pid=1234 comm="httpd"
path="/srv/www/index.html"
scontext=system_u:system_r:httpd_t:s0
tcontext=unconfined_u:object_r:default_t:s0
tclass=file
这里真正有用的信息是:
scontext:发起访问的进程 domain;tcontext:目标对象标签;tclass:对象类别;{ read }:被拒绝的权限;path:辅助定位路径。
第二步:检查 DAC 和实际标签
namei -l /srv/www/index.html
ls -ldZ /srv /srv/www
ls -lZ /srv/www/index.html
namei -l 能逐级展开路径权限。很多问题并不是文件本身,而是父目录缺少执行权限,或目录标签错误。
第三步:判断是配置错误还是确实需要扩权
如果文件应当是 Web 静态内容,通常应修正标签:
sudo semanage fcontext -a -t httpd_sys_content_t '/srv/www(/.*)?'
sudo restorecon -Rv /srv/www
如果应用需要写入上传目录,应只给专用目录配置可写类型,而不是把整个应用树标记为可写。
第四步:验证最小修复
sudo systemctl restart httpd
curl -f http://127.0.0.1/
sudo ausearch -m AVC -ts recent
如果业务成功且没有新的相关拒绝,才说明修复路径正确。
audit2why 可以解释拒绝原因:
sudo ausearch -m AVC -ts recent | audit2why
audit2allow 可以根据拒绝事件生成本地策略,但它只回答“如何允许曾经发生的访问”,不回答“这个访问是否应该发生”。直接安装其生成的规则,可能把入侵行为永久加入白名单。只有确认访问是业务必需、路径和主体范围已收敛时,才应审查后使用本地策略包。
2.6 SELinux 的常见故障边界
- 文件从临时目录复制到目标目录后标签可能不符合目标路径;
- 应用通过符号链接访问时,必须同时检查实际目标和父目录;
- 容器挂载主机目录时,容器运行时可能需要
:Z或:z等标签处理方式,具体取决于 Podman/Docker 和发行版集成; - 仅修改
chmod无法修复 SELinux 类型错误; - 关闭 SELinux 可能让问题暂时消失,但同时移除了一个独立防线,也会改变后续排障条件。
三、AppArmor:基于程序路径的强制访问控制
3.1 AppArmor 的模型
AppArmor 主要把程序路径作为 profile 关联依据,并在 profile 中声明该程序可以访问哪些路径、网络能力和 Linux 能力。
一个简化 profile:
#include <tunables/global>
/usr/local/bin/report-worker {
#include <abstractions/base>
/etc/report-worker/config.yml r,
/var/lib/report-worker/** rw,
/var/log/report-worker/** w,
network inet stream,
capability net_bind_service,
}
语义要点:
- profile 通常绑定
/usr/local/bin/report-worker; r表示读取,w表示写入;/var/lib/report-worker/**覆盖目录下的层级路径;network inet stream允许 IPv4/IPv6 流式网络;capability net_bind_service允许使用对应 Linux capability;- 未声明的访问默认拒绝或记录,取决于 profile 当前模式。
AppArmor 与 SELinux 的关键差异不是“一个更安全”,而是策略表达方式不同:
| 方面 | SELinux | AppArmor |
|---|---|---|
| 主要关联方式 | 进程 domain 与对象安全标签 | 程序路径与路径规则 |
| 文件迁移处理 | 标签可随对象继承或恢复 | 规则通常直接匹配访问路径 |
| 策略表达 | 类型、domain、class、permission | 路径、能力、网络、规则 |
| 常见默认发行版 | RHEL、Fedora | Ubuntu、部分 Debian |
| 诊断事件 | AVC | DENIED,常见于内核日志和 audit 日志 |
两者都属于 MAC,通常不应在同一主机上把它们当成两个独立 profile 系统叠加管理;应以发行版默认安全框架为主。
3.2 查看、加载和切换 profile
查看状态:
sudo aa-status
查看已加载 profile:
sudo apparmor_status
工具名称和安装包在发行版之间可能不同。
常见 profile 操作:
sudo aa-complain /etc/apparmor.d/usr.local.bin.report-worker
sudo aa-enforce /etc/apparmor.d/usr.local.bin.report-worker
sudo apparmor_parser -r /etc/apparmor.d/usr.local.bin.report-worker
complain:记录违规但通常不阻断;enforce:记录并阻断;apparmor_parser -r:重新加载 profile。
从 complain 切换到 enforce 前,要覆盖真实业务路径:启动、读取配置、写日志、读证书、访问 socket、连接数据库、处理异常和升级。只测试“服务能启动”不足以发现运行中路径。
3.3 AppArmor 拒绝的诊断
先查看内核日志或 audit 记录:
sudo journalctl -k -g 'apparmor="DENIED"'
sudo ausearch -m APPARMOR -ts recent
典型事件会显示:
apparmor="DENIED" operation="open"
profile="/usr/local/bin/report-worker"
name="/etc/report-worker/config.yml"
requested_mask="r"
denied_mask="r"
诊断时要区分:
profile:哪个程序 profile 拒绝;name:实际访问路径;requested_mask:程序请求的操作;denied_mask:被拒绝的操作;operation:open、exec、capable、network 等。
如果配置文件路径确实是业务需要,可在 profile 中加入最小规则并重新加载。不要把整个目录写成:
/** rw,
这种写法会让 profile 失去大部分约束价值。即使使用 aa-logprof 自动生成规则,也应逐条审查,因为自动工具只能根据观察到的行为推断“曾经发生过”,不能判断“是否应当允许”。
3.4 路径模型的边界
AppArmor 的路径模型带来几个实际边界:
- 程序通过不同路径、硬链接或解释器启动时,可能命中不同 profile;
- 临时文件名、插件目录和动态生成路径容易导致拒绝;
- 规则写得过宽会扩大访问面,写得过窄则在特定功能路径失败;
- profile 只保护已被 profile 约束的程序。一个未受约束的辅助程序仍可能执行同样的危险操作;
- 即使 AppArmor 允许访问,DAC、capabilities、只读挂载和 seccomp 等其他机制仍可能拒绝。
因此,看到 AppArmor 拒绝时同样要检查传统权限:
namei -l /etc/report-worker/config.yml
ls -l /etc/report-worker/config.yml
ps -o pid,user,group,comm -C report-worker
四、SELinux 与 AppArmor 的选择、迁移和故障处理
4.1 选择原则
优先使用发行版默认和已集成的 MAC 框架:
- RHEL 系统通常已有大量 SELinux policy、文档和运维工具;
- Ubuntu 系统通常已有 AppArmor profile 和 systemd 集成;
- 强行切换框架不仅是安装软件,还涉及启动参数、文件标签、策略覆盖和已有运维流程。
安全能力最终取决于:
其中任一项接近零,整体效果都会显著下降。安装了 SELinux 但处于 disabled,或安装了 AppArmor 但关键服务没有 profile,都不能称为有效控制。
4.2 从宽松状态进入强制状态
迁移的正确顺序是:
- 盘点关键服务和文件路径;
- 在测试环境启用或加载策略;
- 先使用 permissive/complain 收集合法业务访问;
- 检查拒绝事件和异常扩权;
- 修正标签、profile 和应用配置;
- 在维护窗口切换 enforcing/enforce;
- 进行业务验证和回滚演练。
不要把“日志里没有拒绝”直接理解为“策略完整”。如果测试流量没有覆盖备份、故障切换、证书轮换、日志切割和升级流程,策略可能只是在未测试路径上保持沉默。
4.3 强制模式下服务突然失败的恢复路径
当切换强制模式后服务失败:
- 先判断是应用错误、DAC 错误还是 MAC 拒绝;
- 保留拒绝事件,不要先清空日志;
- 恢复到已知安全的 permissive/complain 或回滚最近策略;
- 修正具体标签或规则;
- 在测试流量下再次验证;
- 重新进入强制模式。
SELinux 可临时执行:
sudo setenforce 0
# 只用于诊断或紧急恢复
sudo setenforce 1
这不是长期修复。AppArmor 则应针对具体 profile 使用 complain,而不是关闭整个 AppArmor 服务。
五、审计:记录“谁在什么时候做了什么”
5.1 审计与普通日志不是一回事
普通应用日志回答:
用户 alice 登录失败
内核审计更关注:
哪个进程、以什么 UID、对哪个 inode 或路径、执行了什么系统调用、结果如何
审计事件通常由内核 audit 子系统生成,再由 auditd 写入日志。它适合记录安全相关行为和调查证据,但不是万能的业务审计系统:
- 它不能自动理解业务语义;
- 事件量过大可能影响存储和分析;
- 容器、用户命名空间和多层日志转发会增加归属判断难度;
- 关闭或篡改审计的攻击者可能破坏本机证据,因此日志应尽量远程转发。
查看服务:
sudo systemctl status auditd
sudo auditctl -s
auditctl -s 可看到启用状态、丢失事件数量等信息。重点关注 lost;如果大于零,说明已有事件丢失,不能把当前日志当作完整证据。
5.2 规则的粒度与成本
审计规则可以按系统调用、路径、用户 ID、架构等条件过滤。比如记录 root 身份执行程序:
sudo auditctl -a always,exit -F arch=b64 \
-S execve -F euid=0 -k root-exec
查询:
sudo ausearch -k root-exec -i
这里:
arch=b64表示 64 位系统调用;execve记录程序执行;euid=0过滤有效 UID 为 root;-k root-exec添加查询标签;-i将 UID、syscall 等数值转换成人类可读形式。
在支持 32 位程序的 64 位系统上,还需评估是否增加 arch=b32 规则。规则越宽,事件越多;不能只复制网上规则而不测量。
针对关键文件的系统调用审计示例:
sudo auditctl -w /etc/ssh/sshd_config -p wa -k sshd-config
含义:
-w监控路径;-p wa关注写入和属性变化;-k添加查询关键字。
某些新版本文档更推荐使用基于 syscall 的规则,而不是大量 path watch;具体语法和性能取舍取决于内核与 audit 工具版本。生产中应把规则写入 /etc/audit/rules.d/,由发行版提供的加载机制持久化,而不是只执行一次 auditctl。
例如:
sudo tee /etc/audit/rules.d/50-security.rules >/dev/null <<'EOF'
-a always,exit -F arch=b64 -S execve -F euid=0 -k root-exec
-w /etc/ssh/sshd_config -p wa -k sshd-config
EOF
sudo augenrules --load
sudo auditctl -l
augenrules 是否存在以及加载方式受发行版影响,应先确认:
command -v augenrules
5.3 审计规则的持久性和不可变模式
审计规则本身也是安全配置。可以在规则末尾使用:
-e 2
这会使规则不可变,修改通常需要重启。它能防止运行中的进程轻易清空或改写规则,但也会增加误配置恢复成本。实践中应:
- 在测试机验证完整规则;
- 确认磁盘空间、日志轮转和远程转发;
- 再启用不可变模式;
- 准备带外控制台或重启恢复路径。
auditd 日志路径通常为:
sudo ls -lh /var/log/audit/
检查轮转和空间策略,避免磁盘写满后影响系统。不同 disk_full_action、space_left_action 配置可能导致记录、暂停、单用户模式或关机等行为,生产取值必须结合可用性要求审查,不能盲目追求“日志绝不丢失”。
5.4 读取审计事件
常用查询:
sudo ausearch -m USER_LOGIN -ts today -i
sudo ausearch -m AVC -ts today -i
sudo ausearch -k sshd-config -i
sudo aureport --auth --success
sudo aureport --auth --failed
审计事件可能由多个记录组成,例如一次系统调用关联 SYSCALL、PATH、CWD、PROCTITLE 等记录。不要只解析单行;应使用 ausearch 或集中式解析器按事件 ID 关联。
对高价值行为建议重点关注:
- SSH 登录成功和失败;
- root 或高权限账户执行;
- 身份、sudo、SSH 配置变更;
- 关键系统文件修改;
- 服务启停和 unit 文件修改;
- SELinux AVC 或 AppArmor DENIED;
- 新建用户、修改组和授权文件;
- 防火墙规则变更。
审计记录“发生了什么”,检测系统还要回答“是否异常”。例如凌晨一次合法自动化部署修改了 sshd 配置,单看事件无法判定恶意;需要结合变更单、执行主体、来源 IP、命令行和时间窗口。
六、漏洞治理:从清单到验证的闭环
6.1 漏洞、补丁和风险不是同一个概念
CVE 是漏洞标识,不是风险分数,也不保证所有受影响系统都可被利用。实际风险至少取决于:
Exposure:服务是否可达、是否暴露公网;Exploitability:是否需要认证、特殊配置或本地访问;Impact:权限提升、数据泄露、远程执行还是拒绝服务;Uncertainty:资产版本、补丁回移和实际配置是否已确认。
发行版可能把上游修复回移到旧版本,因此不能只根据上游软件的版本号判断是否有漏洞。应查看发行版安全公告和包 changelog。
6.2 资产清单必须包含“运行状态”
仅记录“安装了 openssl 3.x”不够,还应记录:
- 主机、镜像或虚拟机身份;
- 发行版和支持生命周期;
- 已安装包及版本;
- 实际运行进程和监听端口;
- 服务是否暴露公网;
- 配置变更和例外;
- 最近补丁时间;
- 内核和需要重启的用户态服务;
- 是否存在容器内重复的脆弱依赖。
包清单示例:
# Debian/Ubuntu
dpkg-query -W -f='${binary:Package}\t${Version}\n' > packages-debian.txt
# RPM 系
rpm -qa --qf '%{NAME}\t%{VERSION}-%{RELEASE}\n' > packages-rpm.txt
运行态清单:
uname -a
cat /etc/os-release
ps -eo user,pid,ppid,comm,args --sort=user
ss -lntup
systemctl list-units --type=service --state=running
这些输出应进入资产管理或配置管理系统,而不是只保存在管理员笔记本上。
6.3 更新、重启和验证
Debian/Ubuntu:
sudo apt update
apt list --upgradable
sudo apt upgrade
RHEL/Fedora:
sudo dnf check-update
sudo dnf updateinfo list updates security
sudo dnf upgrade
check-update 返回非零状态有时只表示存在可更新包,不应简单当作命令失败;自动化脚本需要按工具文档处理返回码。
更新后至少验证:
systemctl --failed
sudo ss -lntup
sudo journalctl -p warning -b --no-pager
内核更新通常需要重启才能使用新内核。用户态库更新则可能需要重启仍在使用旧库的进程。可用发行版工具辅助判断,例如某些 RPM 系发行版提供 needs-restarting,但工具包和判断能力有版本差异,不能把命令不存在解释为“无需重启”。
重启前要确认:
- SSH 密钥或带外控制台可用;
- 防火墙、网络和时间同步配置已持久化;
- 数据库和文件系统有正确的停止流程;
- 负载均衡和集群有迁移路径;
- 回滚镜像、旧内核或控制台恢复方案可用。
重启后验证的不只是 uptime:
uname -r
getenforce 2>/dev/null || true
sudo aa-status 2>/dev/null || true
systemctl --failed
sudo ss -lntup
6.4 扫描器的边界
漏洞扫描器常见三类误差:
- 误报:只按版本匹配,没有考虑发行版回移补丁;
- 漏报:无法识别自编译软件、静态链接组件、容器内依赖或运行时下载的模块;
- 配置盲区:知道包版本,却不知道服务是否启用、端口是否暴露、认证是否开启。
因此治理闭环应是:
flowchart LR
A[资产发现] --> B[软件与配置清单]
B --> C[公告与漏洞匹配]
C --> D[按暴露面和影响排序]
D --> E[测试补丁与配置修复]
E --> F[生产发布]
F --> G[服务、端口、日志验证]
G --> H[重新扫描与关闭工单]
H --> B
关键路径是:扫描结果不能直接等同于修复结果。修复后需要确认实际运行进程已加载新库、服务已恢复预期安全配置,且没有因为修复引入新的监听端口或策略错误。
6.5 例外治理
无法立即修复时,例外必须记录:
- 受影响资产和软件版本;
- CVE 或公告编号;
- 暴露路径和业务影响;
- 临时缓解措施;
- 责任人和到期时间;
- 验证方式;
- 下一次复审条件。
临时缓解可以包括:
- 防火墙限制来源;
- 关闭非必要功能;
- 降低服务权限;
- 使用 SELinux/AppArmor 限制文件和网络访问;
- 将服务迁移到隔离网络;
- 增加监控和入侵检测。
但缓解措施不应被写成“已修复漏洞”。例如防火墙阻断公网只能降低远程利用暴露面,不能修复本地提权漏洞。
七、把最小化、MAC、审计和漏洞治理连成一条数据流
一个 Web 服务请求的安全路径可以抽象为:
sequenceDiagram
participant C as 客户端
participant FW as 防火墙/nftables
participant S as Web 服务
participant DAC as DAC/ACL
participant MAC as SELinux/AppArmor
participant FS as 文件系统
participant AU as auditd/日志
C->>FW: TCP 连接
FW-->>C: 允许或丢弃
C->>S: HTTP 请求
S->>DAC: 请求读取/写入对象
DAC-->>S: 允许或拒绝
S->>MAC: 进行 MAC 检查
MAC-->>S: 允许或拒绝
S->>FS: 执行实际 I/O
MAC-->>AU: 记录拒绝事件
DAC-->>AU: 记录部分安全事件
S-->>C: 响应或错误
这条路径说明了几个常见误判:
- 端口开放只代表网络层到达,不代表应用访问文件一定成功;
- 文件权限正确不代表 MAC 一定允许;
- MAC 允许不代表应用身份有足够 DAC 权限;
- 日志中出现拒绝不代表攻击,也可能是配置错误;
- 没有日志不代表没有攻击,可能是审计规则缺失、事件丢失或攻击发生在未覆盖组件中。
八、生产环境的验证、回滚与证据保留
8.1 加固前后进行可比较验证
加固前记录基线:
sudo ss -lntup > before-ports.txt
systemctl list-unit-files --type=service --state=enabled > before-enabled.txt
sudo getenforce > before-selinux.txt 2>/dev/null || true
sudo auditctl -l > before-audit-rules.txt
变更后生成同类输出并比较:
sudo ss -lntup > after-ports.txt
systemctl list-unit-files --type=service --state=enabled > after-enabled.txt
sudo diff -u before-ports.txt after-ports.txt
sudo diff -u before-enabled.txt after-enabled.txt
基线不是越严格越好,而是要能解释每个差异:哪个端口关闭了,哪个服务被禁用,哪个新端口因业务发布出现,哪些差异经过批准。
8.2 失败路径要提前设计
典型失败包括:
- 误禁用网络管理或远程管理服务;
- SSH 配置语法错误导致无法登录;
- SELinux 标签错误导致服务无法读取配置;
- AppArmor enforce 后阻断证书、日志或插件访问;
- 审计规则过宽导致磁盘快速耗尽;
- 更新后新内核无法启动;
- systemd 沙箱使服务无法创建必要目录。
修改 SSH 配置后先检查语法:
sudo sshd -t
确认无输出且退出码为 0,再重载:
sudo systemctl reload sshd
Debian/Ubuntu 的服务单元可能名为 ssh,RHEL 系通常为 sshd,应先用:
systemctl list-unit-files | grep -E '^(ssh|sshd)\.'
远程主机变更前,保持现有会话不关闭,并用第二个会话验证新连接。对于 SELinux、AppArmor 和审计规则,准备带外控制台比准备一条“关闭安全功能”的命令更可靠。
8.3 证据的完整性与时间
审计调查依赖准确时间和完整事件链:
timedatectl status
应确保时区、NTP 和日志时间一致。日志最好转发到权限隔离的集中系统,避免攻击者取得主机 root 后同时修改本地应用日志和审计日志。远程日志本身也要考虑:
- TLS 或受保护的传输;
- 接收端访问控制;
- 时间同步;
- 保留周期;
- 丢包和断链告警;
- 敏感数据脱敏。
集中日志不能阻止攻击,只能提高事后调查和检测能力。
九、常见误解与边界
误解一:root 可以访问一切
root 通常能绕过大量 DAC 检查,但 capability、SELinux/AppArmor、只读挂载、namespace、seccomp 和设备权限仍可能限制它。更准确的说法是:root 的传统 DAC 权限很强,不等于拥有所有内核安全能力。
误解二:chmod 777 能解决服务访问失败
它只放宽 DAC,而且没有解决:
- 父目录搜索权限;
- ACL;
- SELinux 标签;
- AppArmor profile;
- 文件系统只读;
- 应用自身拒绝;
- 进程缺少 capability。
它还可能把敏感文件暴露给同机任意用户。
误解三:audit2allow 或 aa-logprof 生成的规则可以直接安装
自动工具根据已观察到的拒绝事件生成“允许规则”。它们不了解业务意图、攻击阶段和最小权限边界。正确流程是确认主体、对象、操作和业务必要性,再收窄路径和权限。
误解四:没有 AVC 或 DENIED 就说明没有安全问题
可能是:
- 相关服务没有被 MAC 约束;
- 审计规则没有覆盖;
- 日志被轮转或丢失;
- 测试流量没有经过危险路径;
- 问题发生在应用层或网络层,而不是 MAC 层。
误解五:漏洞扫描显示“无漏洞”就代表主机安全
扫描通常不能证明没有零日漏洞、错误配置、弱凭据、业务逻辑缺陷或已被入侵。漏洞治理解决的是已知缺陷风险,不是全部安全风险。
误解六:关闭安全框架可以作为永久兼容方案
关闭 SELinux 或 AppArmor 会减少排障表面上的错误,但同时删除独立隔离层。正确做法是查明实际访问需求,修正标签、profile、服务用户、目录权限或应用配置,并在强制模式下重新验证。
十、一个可执行的加固顺序
对一台已有业务的 Linux 主机,可以按以下顺序实施,每一步都保留输出和回滚信息:
- 资产和基线:记录发行版、内核、包、服务、监听端口、账户、MAC 状态和审计状态;
- 网络最小化:关闭不需要的监听,使用 nftables/firewalld 限制来源和目标;
- 服务最小化:停止并禁用未使用服务,确认反向依赖后再卸载软件包;
- 身份最小化:为服务设置专用 UID/GID,避免无必要的 root 运行;
- 文件最小化:使用正确的 mode、ACL、目录布局和 capabilities,而不是全局放权;
- 启用或收紧 MAC:SELinux 使用正确文件类型、端口类型和布尔值;AppArmor 使用最小路径规则;
- 建立审计:优先记录身份变化、特权执行、关键配置修改和 MAC 拒绝,监控事件丢失与磁盘空间;
- 补丁治理:建立包和运行态资产清单,按暴露面和影响排序更新;
- 验证与回滚:检查服务、端口、日志、策略命中和业务关键路径;
- 持续复审:新服务、新包、新例外和新漏洞都回到同一闭环中。
安全加固的核心不是某一个命令,而是让攻击面、权限面、强制策略、审计证据和补丁状态彼此对应:不必要的入口被移除,必要的服务被限制,异常访问有证据,已知缺陷有期限明确的治理过程,并且每次变更都能验证和恢复。
系列导航与关联阅读
- 系列入口:Linux 完整学习路线:从内核与文件系统到网络、性能和生产运维
- 上一篇:Linux 用户认证与提权:账号、PAM、sudo、密码策略和审计
- 下一篇:Linux 进程、线程与信号:生命周期、父子关系、Zombie 和优雅退出
- 延伸:Linux 权限完整指南:UID/GID、mode、umask、ACL 与 capabilities
- 延伸:Linux 防火墙与 NAT:Netfilter、nftables、连接跟踪和规则治理
- 延伸:OpenSSH 完整指南:密钥、Agent、跳板、隧道和服务端加固
官方资料
本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。

评论
0 条讨论