操作系统安全
操作系统安全
复习定位
操作系统安全的核心是隔离——用户态与内核态隔离、进程间地址空间隔离、用户权限隔离。Linux通过这些机制将普通用户和恶意软件的操作限制在最小范围内。即使一个应用程序被攻破——它的破坏范围被capabilities、seccomp、SELinux等机制限制——不能轻易影响整个系统。
最小权限原则
每个用户和进程只应获得完成其任务所必需的最小权限——不作多余的授权。例如运行一个Web服务器——它需要监听TCP 80端口——但不需要修改系统文件——因此不应以root用户运行。将Web服务器绑定到80端口后——可以setuid切换到一个专有用户(如www-data)——该用户仅有Web目录的读写权限——无权访问/etc/shadow。
Capabilities——将root拆分为细粒度权限
传统Unix要么是root(拥有全部权限——UID=0)要么是普通用户(权限很少)。Capabilities将root权限细分为多个独立单元——每个capability控制一种特权操作。例如:
CAP_NET_RAW: 允许创建原始套接字(用于ping、tcpdump等无需完全root)CAP_NET_BIND_SERVICE: 允许将端口绑定到小于1024的端口(HTTP需要的80端口)CAP_SYS_ADMIN: 挂载文件系统、设置内核参数等高级操作(这个capability太大了——通常被视为"作弊")CAP_DAC_OVERRIDE: 忽略文件的DAC权限检查(即可以读写任何文件)
通过setcap cap_net_raw=ep /usr/bin/ping——你可以赋予ping程序CAP_NET_RAW而不必让它拥有SUID root——安全性比SUID高了一个等次——因为即使ping程序有漏洞崩——攻击者也只能建立原始套接字——不能读写任意文件、加载内核模块。
SELinux与AppArmor
Linux的强制访问控制——无论文件所有者如何设置权限——MAC策略将会覆盖全局。
SELinux(美国国家安全局开发)——为每个进程和文件分别分配一个安全上下文——策略定义"哪个进程可以访问哪个文件"。如果Nginx进程(httpd_t)尝试写/etc/shadow——即使Nginx以root运行——SELinux也会阻止(因为规则不允许httpd_t写shadow_t)。SELinux非常强大但配置复杂——很多用户因为不会排错直接禁用。
AppArmor(某些发行版如Ubuntu默认)——通过进程路径定义权限——每个程序可以通过一个配置文件定义它允许读取/写入/执行哪些路径。如/usr/bin/evince的AppArmor配置可限定PDF阅读器只能访问~/Documents目录——不能访问/etc/shadow。
seccomp
seccomp(secure computing mode)限制进程可以调用的系统调用集合。例如——一个计算进程可以禁止使用read/write/open等系统调用——只允许exit和rt_sigreturn。Docker使用seccomp配置容器的白名单——阻止危险系统调用(如mount,kexec_load,ptrace等)。
复习检查
最小权限原则——为什么运行Web服务器和数据库的服务——不应以root用户运行——root用户的过度权限使得任何提权漏洞都可能直接完全攻陷整个系统——最好的做法是为每个服务创建单独的系统账户。
SUID程序的安全困境——一个SUID root的二进制程序——如果代码中存在漏洞(命令注入/路径遍历)——就可能允许攻击者以root身份执行任意操作——capabilities(将
CAP_NET_BIND_SERVICE赋给服务进程)比SUID更精细地减少了风险暴露面。SELinux和AppArmor的区别——SELinux通过安全上下文标记决定访问权限——AppArmor通过程序的路径名/文件路径白名单限制——SELinux可更精确控制——但配置学习曲线陡峭。
seccomp限制系统调用——如果一个进程被seccomp限制禁止调用
open——那么它完全无法打开任何新文件——这种严格的沙箱场景常用于运行不受信任的用户提交的代码(在线编译器/LeetCode提交答案)防止作弊行为。Docker默认禁用非常多的capabilities(如
CAP_SYS_ADMIN、CAP_NET_ADMIN等30多个)——防止容器内部的操作影响宿主机——但Docker同时具备在容器需要(如自定义网络设置)的情况下可以--cap-add添加授权。这种"白名单"模式天然保护了宿主机不受容器内的提权操作。
最小权限原则在进程级别的实践
最小权限原则不仅用于人的账户管理——也用于进程级别的权限控制。在Linux中——每个进程以用户身份运行——继承该用户的所有权限。因此——运行网络服务时应创建专门的服务账户——而不是使用root。例如——Nginx的master进程以root启动(需要绑定80端口)——但工作进程通过setuid()立即切换到nginx用户——后续所有请求的处理都在nginx用户权限下运行——即使工作进程被攻破——攻击者也不能获得root权限。
MySQL/MariaDB在安装时默认创建mysql系统用户——数据库服务以该用户运行——数据库文件的所有者为mysql——不是root——即使被攻入——攻击者只能访问数据库文件——不能直接修改系统文件或提权到root。
容器环境下的最小权限
在容器中——默认以容器内部的root用户运行程序——但容器内的root与宿主机root不是同一个用户(通过Linux User Namespace映射)——容器内root的capabilities被容器运行时限制在非常有限的集合内。Docker默认移除所有危险的capabilities——只保留进程正常运行所必须的最少集合。
在Kubernetes中——可以通过securityContext为Pod设置更细粒度的权限控制:
securityContext:
runAsUser: 1000 # 以UID 1000运行进程
runAsGroup: 3000
allowPrivilegeEscalation: false # 禁止提权
capabilities:
drop: ["ALL"] # 移除所有capabilities
# 需要什么再加什么——不要默认全给这种容器安全配置实践是现代基础设施中操作安全的重要部分。
Capabilities的继承与传递
Capabilities通过cap_bset(bounding set)在父子进程间传递——一个进程fork子进程时——子进程的capabilities不能超出父进程的bounding set。设置cap_bset可以确保即使子进程发生了提权——也无法获得超出界限的capabilities。这些namespace对确保通过网络暴露的容器化环境中运行的应用程序不会在存在漏洞时牵连宿主机和相邻容器有重要作用。
SELinux和AppArmor的实际配置
Linux内核模块签名的安全影响
现代Linux支持内核模块签名——仅允许加载有正确签名的内核模块。Secure Boot启用后——内核本身受签名保护——模块签名也必须在系统可信范围内。这阻止了攻击者加载恶意内核模块(如rootkit)来获得内核级控制权限。安全启动与内核模块签名必须共同配置才有实际效果——因为攻击者也可能从启动链未受保护的其他环节影响整个签名信任链。
复习检查(续)
Linux的内核模块签名和Secure Boot如何协同提供启动时的安全保证——Secure Boot验证bootloader和内核的签名——内核模块签名阻止未授权的模块加载——两者结合确保从硬件到内核的整个引导链是可信的。
Kubernetes securityContext中
allowPrivilegeEscalation:false的作用——禁止容器进程通过setuid或类似机制提高权限——即使容器以root运行——也禁止提权到更高级(正常情况不会出现——但双保险确保进程无法利用setuid位的二进制进行提权攻击)。Linux User Namespace在容器安全中的作用——将容器内的root映射为容器外的非特权用户——使容器内的root在宿主机上只有普通用户的权限——容器突破namespace的限制后也无法直接获得宿主机root。
capability bound set(cap_bset)的作用——限制子进程可以获得的capabilities——即使子进程调用了setcap或exec一个需要额外capabilities的程序——也不能获得超出bound set范围的capabilities。
SELinux和AppArmor两种MAC机制的配置复杂度和安全性的差异——SELinux策略全面但需要详尽理解安全上下文和规则——AppArmor通过简单的路径白名单提供可用的防护——两者的安全性都取决于定义策略的严格程度而非机制本身。
SELinux的安全上下文和执行流程
SELinux为系统中的每个进程(主体)和每个文件/端口/设备(客体)分配一个安全上下文——格式为user:role:type:mls。策略规则定义"主体"type能否对"客体"type执行指定的操作。当进程尝试访问客体时——内核中的SELinux模块检查该操作是否被策略允许——如果策略没有明确允许——则该操作被拒绝(默认拒绝原则)。
SELinux的三种模式:
Enforcing(强制模式): 策略生效——违规操作被拒绝并记录
Permissive(宽容模式): 策略不生效——违规操作被允许但记录(用于调试策略)
Disabled(关闭): SELinux完全禁用——没有任何额外开销和检查配置SELinux策略时——audit2allow工具读取日志中的违规记录——自动生成允许这些操作的新策略模块——简化了策略的编写。
AppArmor的配置示例
一个AppArmor配置文件的示例(/etc/apparmor.d/usr.bin.evince):
#include <tunables/global>
/usr/bin/evince {
#include <abstractions/base>
#include <abstractions/gnome>
# 允许读取PDF文件
/home/** r,
/tmp/** rw,
# 禁止访问系统文件
/etc/shadow r,
/root/** r,
}AppArmor的路径白名单模式更容易理解和编写——但灵活性不如SELinux——不能区分同路径下不同进程的不同操作(如读取 vs 写入需要在路径规则里单独指定rw或r标记)。
内核模块签名对Rootkit的防御
当攻击者尝试加载一个没有签名的内核模块时——如果内核启用了模块签名验证——加载被拒绝——从而阻止了rootkits通过内核模块植入系统。在现代企业级的加固Linux发行版(RHEL,Fedora的SecureBoot和signature enforcement)中——这两个特性默认联动开启。自编译的内核和其他加载驱动需要在系统部署时通过enroll签名到Machine Owner Key(MOK)的方式允许其正常加载——以避免在线上运行时因未签名的内核模块被阻止加载而导致系统功能缺失。
复习检查(续二)
SELinux的默认拒绝原则——未被策略明确允许的操作在内核层面被拒绝——这与传统的DAC(自主访问控制)不同——DAC中只要文件所有者允许——操作就被放行——SELinux提供了基于安全上下文的第二层保护。
AppArmor的路径白名单相对于SELinux安全上下文的优势——配置语法更直观——不需要理解安全上下文等复杂的概念——推荐在Ubuntu等默认集成AppArmor的发行版上逐步推广应用。
内核模块签名如何检测恶意模块——模块加载时内核验证模块的签名是否受信任——未签名的模块被直接拒绝——攻击者无法通过
insmod evil.ko加载内核级rootkit。Secure Boot + 内核模块签名 + SELinux/AppArmor + seccomp 四层防御的关系——这四者从不同的层面提供安全防护——启动链可信(Secure Boot)→内核级访问控制(SELinux/AppArmor)→系统调用过滤(seccomp)——形成了对操作系统安全的纵深防御。
Docker容器中
--privileged参数的安全风险——把一个容器以特权模式运行时——移除了几乎所有capabilities限制——容器可以访问宿主机所有设备——可以加载内核模块——从特权容器逃逸是容器安全研究中最常见的高危路径之一。
用户态与内核态隔离的根本机制
x86-64的分页机制中——每个页表项(PTE)的U/S位(用户/超级用户位)决定该页是否允许用户态程序访问。操作系统将内核空间的页标记为仅内核态可访问(U/S=0)——当用户态程序试图访问这些页时——MMU触发页错误——操作系统终止该程序。
系统调用(syscall)是用户态程序请求内核服务的唯一合法通道。syscall指令提升到Ring0后——内核检查系统调用号和参数——执行对应的内核函数——返回前切换回Ring3。这种严格的特权级分隔保证了用户态程序不能直接修改内核数据结构或执行特权指令(如修改页表基址CR3寄存器)。
复习检查(续三)
特权容器(--privileged)的安全风险——移除了Docker默认的capabilities限制——容器可以访问宿主机所有设备——增加了容器逃逸的攻击面——生产环境不应使用特权容器。
x86-64页表的U/S位的保护机制——用户态程序访问U/S=0的页时MMU触发异常——操作系统终止程序——防止用户态程序读写内核内存空间。
为什么U/S位在页表项中而不是在段描述符中——x86-64长模式下段保护已经弱化——分页是64位模式下内存保护的唯一硬件机制——所有隔离功能都通过分页实现。
内核通过mmap将内核的一小部分直接映射到用户地址空间中(如vdso)——但映射为只读或可执行但不可写——因此用户程序可以快速调用用户态的不含特权的系统调用功能(如gettimeofday不需要切换到内核态)——不影响安全。
CPU的Ring 0/3的特权级和页表的U/S位共同决定了用户态与内核态的安全边界——U/S位的保护只依赖硬件——即使操作系统有漏洞——硬件U/S位保护仍然有效(除非内核手动修改页表否则不会将有问题的页标记给用户态)。因此用户态程序无法绕过U/S位直接访问内核内存——恶意程序必须通过内核漏洞才能获得内核级的执行权限。
操作系统安全的核心总结
操作系统安全的核心是隔离——用户态与内核态隔离(硬件U/S位)、进程间地址空间隔离(独立页表)、用户权限隔离(UID/GID+Capabilities)。这些隔离机制由硬件(MMU/TLB/SMMU)+内核(调度器/中断/系统调用)共同实现——确保即使一个应用程序被攻破——其对系统的破坏范围被限制在最小权限集合内。在实际生产环境中——需要通过"纵深防御"的理念将用户隔离、文件系统权限、MAC强制访问控制、系统调用过滤、模块签名验证等措施层层叠加——以有效提升整体防攻水平。
实际操作中的安全加固检查清单
操作系统层面安全加固实践:
□ 所有服务使用专用系统账户运行——不要使用root
□ 关闭不必要的系统服务——减少攻击面
□ 限制SSH密钥登录——禁止密码登录
□ 配置iptables/nftables防火墙——仅开放必要端口
□ 启用SELinux/AppArmor——配置适应业务需求的策略
□ 安装安全更新——使用自动补丁管理工具(如unattended-upgrades)
□ 设置系统文件完整性检查(AIDE/Tripwire)
□ 配置集中日志收集和告警(如Wazuh/SIEM)
□ 定期使用漏洞扫描工具检查已知漏洞
□ 使用auditd监控关键系统文件和系统调用的安全事件复习检查(续五)
操作系统安全的核心是隔离——用户态与内核态隔离(硬件U/S位)、进程间地址空间隔离(独立页表)、用户权限隔离(UID/GID+Capabilities)。这些隔离机制由硬件(MMU/TLB/SMMU)+内核(调度器/中断/系统调用)共同实现——确保即使一个应用程序被攻破——其对系统的破坏范围被限制在最小权限集合内。在实际生产环境中——需要通过"纵深防御"的理念将用户隔离、文件系统权限、MAC强制访问控制、系统调用过滤、模块签名验证等措施层层叠加——以有效提升整体防攻水平。