首页 / 帮助文档 / Debian运维中systemd-analyze安全选项检查服务沙盒配置

Debian运维中systemd-analyze安全选项检查服务沙盒配置

在Debian 12系统上,使用systemd管理服务时,我们往往只关注ExecStart启动命令是否正确,却忽略了服务运行时的安全边界。执行systemd-analyze security [服务名]命令后,你会看到一长串安全选项的评估结果,其中不少标记为“不安全”或“未设置”。这些选项并非摆设,它们直接决定了进程在系统中的隔离程度。一个配置不当的服务可能拥有访问整个文件系统、修改内核参数甚至加载内核模块的能力,一旦服务存在漏洞被利用,攻击者就能获得极高的系统权限。而通过合理配置沙盒选项,即使服务被攻破,攻击者也被困在一个受限环境中,无法横向移动或持久化驻留。

systemd提供了数十个安全指令,可以直接在服务单元文件中配置。这些指令不需要额外安装AppArmor或SELinux等强制访问控制系统,仅靠内核已有的namespace、cgroups、seccomp和capabilities机制就能实现进程隔离。下面逐一分析最关键的沙盒配置项,给出具体的配置方法和验证手段。

ProtectSystem:控制文件系统可见范围

ProtectSystem是使用频率最高的沙盒指令,它控制服务对操作系统目录的访问权限。该指令有三个可选值:strict、full和true。设置为strict时,/usr和/etc目录以只读方式挂载,/dev、/proc、/sys等虚拟文件系统与宿主机隔离,服务只能看到自己私有的/tmp和/var/tmp。full模式类似,但/dev、/proc、/sys仍然可见。true模式仅将/usr和/etc设为只读。

对于大多数网络服务,推荐使用strict模式。配置方法如下:

[Service]
ProtectSystem=strict

设置后,服务进程无法修改系统二进制文件和配置文件,也无法通过/proc或/sys获取其他进程信息或修改内核参数。如果服务确实需要写入某些目录,可以配合ReadWritePaths指令开放白名单路径。例如一个Web应用需要写入/var/lib/myapp目录:

[Service]
ProtectSystem=strict
ReadWritePaths=/var/lib/myapp

验证配置是否生效,可以重启服务后查看挂载信息。进入服务进程的命名空间检查/proc/mounts,会发现根文件系统被重新挂载为只读,只有指定的白名单路径可写。systemd-analyze security对该项的评估也会从“不安全”变为“完全保护”。

ProtectHome:隔离用户家目录

很多服务根本不需要访问/home、/root和/run/user这些用户数据目录。ProtectHome指令可以完全隔离这些目录。可选值有yes、read-only和tmpfs。yes表示/home、/root、/run/user对服务不可见;read-only表示只读可见;tmpfs表示挂载一个空的tmpfs到这些位置,服务看不到真实数据但可以写入临时数据。

对于系统级服务,建议直接设为yes:

[Service]
ProtectHome=yes

这样即使服务被攻破,攻击者也无法读取用户家目录中的SSH密钥、配置文件或敏感数据。如果你的服务确实需要以某个用户身份运行并访问其家目录,则不应启用此选项。但绝大多数后台服务都不需要这个权限。

PrivateTmp:独立的临时文件空间

这是一个看似简单却极其重要的选项。启用PrivateTmp后,服务进程的/tmp和/var/tmp目录与其他进程完全隔离。服务写入的临时文件对其他进程不可见,反之亦然。这能有效防止通过临时文件进行的进程间攻击,比如经典的/tmp目录下的符号链接竞争攻击。

配置只需一行:

[Service]
PrivateTmp=yes

启用后,systemd会为服务创建私有的挂载命名空间,/tmp实际指向/var/tmp/systemd-private-xxxxx-/tmp这样的路径。服务内部看到的/tmp只有自己创建的文件。systemd-analyze security检测到PrivateTmp未启用时,会给出“不安全”的评估,因为共享临时文件目录确实存在较大的攻击面。

NoNewPrivileges:禁止权限提升

这个选项阻止服务进程及其子进程通过setuid、setgid位或文件系统capabilities获得额外权限。即使服务内部执行了一个设置了setuid位的程序,该程序也不会获得提升后的权限。这是一个简单有效的防护措施,配置方法:

[Service]
NoNewPrivileges=yes

绝大多数服务都不需要动态提升权限,因此这个选项几乎可以无脑启用。启用后,systemd-analyze security会显示该项为“完全保护”。这是systemd沙盒配置中性价比最高的选项之一,零副作用,安全收益显著。

CapabilityBoundingSet:精简进程权能

Linux将root的超级权限拆分为多个细粒度的capabilities,比如CAP_NET_BIND_SERVICE允许绑定1024以下的特权端口,CAP_SYS_PTRACE允许跟踪其他进程。默认情况下,以root启动的服务拥有全部capabilities,即使服务后来切换到普通用户,这些capabilities也可能被子进程继承。

CapabilityBoundingSet指令可以限制服务进程能获得的capabilities集合。一个典型的网络服务可能只需要:

[Service]
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_RAW

如果服务不需要绑定特权端口,甚至可以完全清空:

[Service]
CapabilityBoundingSet=

确定服务需要哪些capabilities,可以使用strace或perf trace追踪服务启动过程中的系统调用,观察哪些操作因为权限不足而失败。systemd-analyze security会列出当前服务拥有的capabilities,过多的capabilities会被标记为不安全。合理精简后,即使服务以root身份运行,其实际能力也被大幅限制。

RestrictAddressFamilies:限制网络协议族

很多服务只需要IPv4和IPv6的TCP或UDP套接字,但默认情况下进程可以创建任意协议族的套接字,包括AF_NETLINK(用于操作网络接口和路由表)、AF_PACKET(原始套接字)、AF_UNIX等。RestrictAddressFamilies指令可以精确控制服务能使用的协议族。

一个只需要TCP套接字的HTTP服务可以这样配置:

[Service]
RestrictAddressFamilies=AF_INET AF_INET6

如果需要Unix域套接字与本地进程通信,加上AF_UNIX:

[Service]
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX

这个限制通过seccomp过滤器实现,服务进程尝试创建被禁止类型的套接字时,系统调用会直接返回错误。这能有效防止攻击者在服务内部创建原始套接字进行ARP欺骗或DNS劫持等网络攻击。

ProtectKernelTunables和ProtectKernelModules:保护内核安全

这两个选项分别禁止服务修改内核运行参数和加载卸载内核模块。ProtectKernelTunables将/sys和/proc/sys挂载为只读,ProtectKernelModules则禁止使用init_module和delete_module等系统调用。

[Service]
ProtectKernelTunables=yes
ProtectKernelModules=yes

几乎没有哪个普通服务需要修改内核参数或加载模块。启用这两个选项可以防止攻击者通过修改内核参数关闭安全特性,或者加载恶意内核模块实现持久化。systemd-analyze security对这两个选项的检查非常严格,未启用时一定会给出警告。

PrivateDevices:隔离设备节点

启用PrivateDevices后,服务进程只能看到少量虚拟设备节点,如/dev/null、/dev/zero、/dev/random等。真实的物理设备、磁盘分区、终端设备对服务完全不可见。配置方法:

[Service]
PrivateDevices=yes

这能防止服务访问磁盘原始设备绕过文件系统权限,或者控制终端设备进行键盘记录等攻击。不过需要注意,如果服务需要访问/dev/dri(GPU渲染)、/dev/snd(音频设备)等特定设备,则需要配合DeviceAllow指令开放白名单。

ProtectHostname:保护主机名

这个选项将主机名设置为只读,服务进程无法通过sethostname系统调用修改系统主机名。虽然主机名修改看起来危害不大,但在某些网络环境中,主机名变化可能导致监控系统混乱或身份认证失败。

[Service]
ProtectHostname=yes

启用后,服务内的hostname命令读取的仍然是真实主机名,但任何修改操作都会被拒绝。

LockPersonality:锁定进程执行域

personality系统调用允许进程切换执行域,模拟不同Unix系统的行为。这个功能在现代Linux系统中几乎用不到,但攻击者可能利用它绕过某些安全限制。LockPersonality指令禁止服务进程调用personality:

[Service]
LockPersonality=yes

这是一个低风险但容易配置的选项,建议默认启用。

RestrictRealtime:禁止实时调度

实时调度策略允许进程获得极高的CPU调度优先级,可能导致系统其他进程饥饿甚至系统无响应。普通服务不需要实时调度能力:

[Service]
RestrictRealtime=yes

启用后,服务进程无法通过sched_setscheduler切换到SCHED_FIFO或SCHED_RR等实时调度策略。

MemoryDenyWriteExecute:禁止同时可写可执行的内存

这是防止代码注入攻击的重要选项。启用后,进程不能创建同时具有写和执行权限的内存映射。这意味着攻击者无法将shellcode写入内存后直接执行。配置方法:

[Service]
MemoryDenyWriteExecute=yes

不过需要注意,某些使用JIT编译技术的程序(如Java虚拟机、Node.js的V8引擎、浏览器的JavaScript引擎)需要同时可写可执行的内存。对于这类程序,不能启用此选项。但对于用C、Go、Rust等编译型语言编写的服务,通常可以安全启用。

SystemCallFilter:系统调用白名单

这是systemd沙盒中最强大也最复杂的选项之一。SystemCallFilter允许你定义服务进程可以使用的系统调用白名单或黑名单。一个简单的白名单配置:

[Service]
SystemCallFilter=~@clock @debug @module @mount @raw-io @reboot @swap @privileged @obsolete
SystemCallErrorNumber=EPERM

这里的~表示黑名单模式,@clock、@debug等是systemd预定义的系统调用组。@privileged组包含了所有可能提升权限的调用,@mount包含挂载相关调用,@raw-io包含直接I/O操作。SystemCallErrorNumber指定了违反规则时返回的错误码,默认是EPERM。

systemd预定义了多个系统调用组,可以通过systemd-analyze syscall-filter查看完整列表。对于特定服务,可以先使用白名单模式,只允许服务实际使用的系统调用:

[Service]
SystemCallFilter=@basic-io @file-system @network-io @process @signal
SystemCallErrorNumber=EPERM

确定服务需要哪些系统调用,可以使用strace追踪服务运行时的调用模式,然后根据追踪结果构建最小化的白名单。这个过程比较耗时,但安全收益极高。

使用systemd-analyze security评估整体安全状态

配置完成后,使用systemd-analyze security [服务名]可以查看服务的安全评分和各项选项的详细状态。命令输出会列出所有安全选项,并用颜色标记:绿色表示已安全配置,黄色表示部分保护,红色表示未设置或不安全。最上方会给出一个总体暴露评分,分数越低越安全。

例如一个经过全面沙盒配置的Nginx服务,评分可以从默认的9.0以上降至2.0以下。命令还会给出具体的改进建议,比如哪些选项尚未配置,哪些capabilities可以进一步精简。

对于系统中所有运行的服务,可以用以下命令批量检查:

systemd-analyze security '*' 2>/dev/null | grep -E '^(NAME|✗|✓|→)' | less

这会列出所有服务的安全评估概要,方便找出安全配置最薄弱的服务进行优先加固。

实际配置案例:一个加固后的Web服务

以下是一个完整的安全沙盒配置示例,适用于用Go语言编写的REST API服务,该服务监听8080端口,需要读取/etc/myservice/config.yaml配置文件,写入/var/log/myservice/日志目录:

[Unit]
Description=My API Service
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=myservice
Group=myservice
ExecStart=/usr/local/bin/myapi
Restart=on-failure
RestartSec=5s

# 文件系统隔离
ProtectSystem=strict
ReadWritePaths=/var/log/myservice
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes

# 权限控制
NoNewPrivileges=yes
CapabilityBoundingSet=
AmbientCapabilities=

# 内核保护
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes

# 进程限制
ProtectHostname=yes
LockPersonality=yes
RestrictRealtime=yes
MemoryDenyWriteExecute=yes
RestrictSUIDSGID=yes
RemoveIPC=yes

# 网络限制
RestrictAddressFamilies=AF_INET AF_INET6

# 系统调用过滤
SystemCallFilter=@basic-io @file-system @network-io @process @signal
SystemCallErrorNumber=EPERM

[Install]
WantedBy=multi-user.target

这个配置将服务牢牢限制在最小权限范围内。服务只能读取/etc下的配置文件,只能写入指定的日志目录,不能访问用户数据,不能修改内核参数,不能加载模块,不能创建原始套接字,不能提升权限,不能使用实时调度。即使服务存在远程代码执行漏洞,攻击者也几乎无法在系统层面造成实质性危害。

配置完成后执行systemctl daemon-reload重载配置,然后重启服务。使用systemd-analyze security myapi.service检查评分,应该能看到显著改善。如果服务启动失败,使用journalctl -u myapi.service -f查看日志,根据错误信息调整配置。常见问题包括:ReadWritePaths路径不存在或权限不正确、SystemCallFilter过于严格导致服务需要的系统调用被阻止、MemoryDenyWriteExecute与JIT编译器冲突等。

沙盒配置不是一蹴而就的,建议先从影响最小的选项开始,逐步收紧限制。每次修改后用systemd-analyze security验证改进效果,同时确保服务功能正常。这种渐进式加固方法既能提升安全性,又不会因为过度限制导致服务不可用。