NSA 安全增强型 Linux 中的网络
SELinux 为网络系统提供强大的通用安全性。它允许系统被严格锁定,使服务仅具有运行所需的最小权限集。这种最小权限原则的实施有助于遏制因代码错误、恶意代码、用户错误和恶意用户引起的安全漏洞。
例如,面向外部的 Web 服务器通常会通过多种方式进行强化,包括
禁用不必要的服务。
在 chroot 监狱中运行服务器软件。
使用 iptables 进行本地数据包过滤。
使用 sudo 进行权限管理。
锁定配置文件。
这是一种良好的、多层次的安全方法,实施了纵深防御原则。
SELinux 添加了另一个安全层,强制访问控制 (MAC)。标准 SELinux 通过类型强制 (TE) 结合基于角色的访问控制 (RBAC) 来实现 MAC,它受中央管理的、由内核强制执行的安全策略控制。与传统的 UNIX 安全性不同,普通用户对 SELinux 安全策略没有任何控制权(因此称为强制性),而超级用户 root 可以被划分为隔离的管理角色,供授权用户使用,称为职责分离。
传统的自主访问控制 (DAC) 进一步受到 TE 模型的限制,该模型将类型分配给操作系统对象,例如进程、文件和网络资源,然后定义它们之间交互的规则。(进程的类型通常被称为域。)这允许细粒度的访问控制,将最小权限原则的范围扩展到典型 OS 强化的范围之外。
SELinux 构建于 2.6 内核中的 LSM(Linux 安全模块)和 Netfilter API 之上。LSM 和 Netfilter 都是访问控制框架,由内核内战略性定位的挂钩点组成。内核流从这些挂钩重定向到安全模块(如 SELinux),这些模块执行访问控制计算并将裁决返回给挂钩。挂钩使用从安全模块返回的裁决来决定是允许正常的内核流继续还是阻止它。
SELinux 的核心设计原则之一是在 OS 对象级别调解访问。SELinux 不是采用幼稚的方法,让安全监视器决定程序是否可以使用某些参数执行特定的系统调用,而是查看程序执行期间的完整安全上下文、附加到被访问对象的安全标签以及正在执行的操作。例如,ls由系统管理员运行与ls由普通用户运行是不同的。
SELinux 权限的一般形式是
action (source context) (target context):(target object classes) permissions
以下是 SELinux 策略中的一个示例
allow bluetooth_t self:socket listen;
这为 bluetooth_t 域提供了对其自身安全上下文标记的套接字的 listen 权限。因此,在 bluetooth_t 域中运行的进程被允许在其拥有的套接字上调用 listen()。
self 指定符只是一种简写方式,用于使目标上下文与源上下文相同。这通常用于与套接字相关的策略中,因为它们的标签通常与创建进程的安全上下文相同。
套接字由其关联的 inode 标记,并分为通用套接字或以下套接字子类之一
UNIX 流。
UNIX 数据报。
TCP。
UDP。
原始(包括 ICMP 和其他非 TCP/UDP)。
Netlink 族。
数据包。
密钥 (pfkeyv2)。
套接字的子类可以在安全策略中区分,从而为不同的网络协议提供细粒度的控制和灵活性
allow lpd_t printer_port_t:tcp_socket name_bind;
IPv4 和 IPv6 端口在内核中隐式标记,如策略所指定。标记端口类型的格式是
portcon protocol { port number | port range } context
以下定义了用于标记标准打印机端口的安全上下文
portcon tcp 515 system_u:object_r:printer_port_t
default_msg_context 参数旨在用于标记到达接口的消息,但目前未使用。
以下是策略中 netif 标记的一些示例
netifcon eth0 system_u:object_r:netif_intranet_t [...] netifcon eth1 system_u:object_r:netif_extranet_t [...]
在 SELinux 下,节点指的是 IPv4 或 IPv6 地址和网络掩码。它允许通过策略使用安全上下文标记主机和网络地址。
标记节点的格式是
nodecon address mask context
以下是标记 localhost 地址的示例
nodecon 127.0.0.1 255.255.255.255 system_u:object_r:node_lo_t nodecon ::1 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff system_u:object_r:node_lo_t
访问控制挂钩是为每个套接字系统调用实现的,允许所有基于套接字的协议都由 SELinux 策略进行调解。一些挂钩仅用于内务处理,但在其他情况下,挂钩通常用于检查一个或多个访问控制权限。
由于有大量的通用套接字控件,请参阅表 1,了解挂钩、套接字系统调用和权限之间的关系。
表 1. 挂钩、套接字系统调用和权限之间的关系
挂钩 | 系统调用 | SELinux 权限 |
---|---|---|
selinux_socket_create | socket | create |
selinux_socket_post_create | socket | 不适用 |
selinux_socket_bind | bind | bind |
selinux_socket_connect | connect | connect |
selinux_socket_listen | listen | listen |
selinux_socket_accept | accept | accept |
selinux_socket_sendmsg | sendmsg、send、sendto | write |
selinux_socket_recvmsg | recvmsg、recv、recvfrom | read |
selinux_socket_getsockname | getsockname | getattr |
selinux_socket_getpeername | getpeername | getattr |
selinux_socket_setsockopt | setsockopt | setopt |
selinux_socket_getsockopt | getsockopt | getopt |
selinux_socket_shutdown | shutdown | shutdown |
在内部,socket() 系统调用被分解为两个挂钩。selinux_socket_create 挂钩用于检查进程是否可以创建请求类型的套接字。selinux_socket_post_create 管理挂钩用于为与套接字关联的新分配的 inode 分配安全标签和套接字类。
SELinux 权限也抽象了从安全角度查看系统调用和其他操作的方式。请注意,例如,getattr 权限用于 getsockname() 和 getpeername() 系统调用。SELinux 认为它们在安全方面是等效的。类似地,所有基于 sendmsg() 和 recvmsg() 的系统调用为了安全管理目的而被简化为简单的 read 和 write。对于好奇的人,这些挂钩背后的代码可以在 2.6 内核的 security/selinux/hooks.c 中找到。
由于套接字也是文件,因此它们继承了一些与文件关联的访问控制。表 2 列出了套接字继承的特定于文件的挂钩和权限。
在 Linux 下,UNIX 域套接字可以在独立于文件系统的抽象命名空间中创建。已实施额外的挂钩以允许调解抽象命名空间中 UNIX 域套接字之间的通信,以及提供对 UNIX 域通信方向性的控制。当一个 UNIX 域套接字尝试与另一个套接字建立流连接时,selinux_socket_unix_stream_connect 挂钩会检查 connectto 权限。当一个 UNIX 域套接字向另一个套接字传输数据报时,selinux_socket_unix_may_send 挂钩会检查 sendto 权限。
Linux 下 UNIX 域套接字的另一个功能是使用 SO_PEERCRED 套接字选项对等方进行身份验证。这会获取对等方的用户 ID、组 ID 和进程 ID。在 SELinux 下,我们还可以通过新的套接字选项 SO_PEERSEC 获取对等方的安全上下文。使用此选项调用 getsockopt(2) 会调用 selinux_socket_getpeersec 挂钩,该挂钩将安全上下文复制到用户传入的缓冲区中。这用于本地 IPC,例如安全增强型 DBUS。
Netlink 套接字提供基于消息的用户/内核通信。例如,它们用于配置内核路由表和 IPSec 机制。
Netlink 通信是异步的;消息可以在一个上下文中发送,并在另一个上下文中接收。当传输 Netlink 数据包时,发送者的安全凭据(以能力集的形式)与数据包一起存储,并在接收时进行检查。例如,这允许内核路由代码确定发送路由表更新的用户是否真的被允许这样做。
作为 LSM 项目的一部分,能力逻辑已从核心内核代码中移出并移入安全模块,以便 LSM 可以根据需要实施不同的安全模型。
SELinux 模块使用 selinux_netlink_send 挂钩仅将 NET_ADMIN 能力复制到发送到内核的 Netlink 数据包。
当接收到安全关键消息时,会调用 selinux_netlink_recv 挂钩。SELinux 使用此挂钩来验证 NET_ADMIN 能力是否在传输期间复制到数据包中,从而验证发送进程是否具有该能力。
越来越多的 Netlink 族正在被实施,SELinux 为那些安全关键的 Netlink 套接字定义了子类。这允许在每个 Netlink 族的基础上配置套接字控件(例如,区分路由消息和内核审计消息)。
SELinux 还可以通过使用 selinux_netlink_send 挂钩来确定某些类型的 Netlink 套接字上的消息是读取还是写入操作,然后分别应用 nlmsg_read 或 nlmsg_write 权限。这允许细粒度的策略来指定,例如,域可以读取路由表但不能更新它。
SELinux 为 TCP、UDP 和 Raw 套接字子类添加了几个控件。node_bind 权限确定套接字是否可以绑定到特定类型的节点。这显然仅对本地 IP 地址有用,并且可以用于限制守护进程绑定到特定的 IP 地址。
name_bind 权限控制套接字是否可以绑定到特定类型的端口。仅当端口号超出本地端口范围时才调用此权限。本地端口范围是内核自动分配端口号的范围(例如,在为传出的 TCP 连接选择源端口时),并且可以通过 sysctl net.ipv4.ip_local_port_range 进行配置。在典型的系统上,此范围是
$ sysctl net.ipv4.ip_local_port_range net.ipv4.ip_local_port_range = 32768 61000
因此,仅当套接字绑定到此范围之外的端口时才调用 name_bind。SELinux 始终为低于 1024 的端口调用权限,而与 sysctl 设置无关。这两个与绑定相关的控件都从 selinux_socket_bind 挂钩中调用,该挂钩通过 bind(2) 系统调用调用。
send_msg 和 recv_msg 权限用于控制套接字是否可以通过特定类型或端口发送或接收消息。
实现了一组权限,用于控制是否可以通过 TCP、UDP 或 Raw 套接字为特定类型的 netif 和节点对象接收或发送数据包。这些是 tcp_send、tcp_recv、udp_send、udp_recv、rawip_send 和 rawip_recv。
这些基于消息的控件在 selinux_sock_rcv_skb 挂钩处为传入数据包调用,这是网络堆栈中我们可靠地将数据包与接收者套接字关联的第一个点。对于传出数据包,SELinux 注册一个 Netfilter 挂钩并在 IP 层捕获它们;传出数据包在此阶段仍然附加了套接字所有权信息。
以上所有控件都是协议无关的,因为它们在 IPv4 和 IPv6 协议上都运行。
我们现在已经介绍了足够的理论,可以查看一个简单网络应用程序的 SELinux 策略的真实示例。由于空间限制和现实世界网络的复杂性,我们为简单的 TCP 回显客户端开发策略。
客户端的源代码可在本文的在线资源中列出的网站上找到。简而言之,它创建一个 TCP 套接字,连接到远程主机的回显端口,写入一些文本,然后将其读回。
我的工作站有两个以太网接口,在本例中,eth0 在内联网上,我连接的服务器的 IP 地址为 10.3.1.2。
以下是安全策略的目标
仅授予客户端绝对需要的 OS 访问权限。
允许客户端仅通过 eth0 与 10.3.1.0/24 子网上的 inetd 服务器通信。
以下是满足这些目标的带注释的安全策略。要使用它,请为您的发行版安装 SELinux 策略源包,然后cd到顶层目录(在我的工作站上是 /etc/selinux/strict/src/policy)。
创建一个名为 domains/program/echoclient.te 的文件,并添加这些策略条目,如清单 1 所示。
清单 1. echoclient.te
# Simple echoclient policy for Linux Journal article # File: domains/program/echoclient.te # Define the echoclient_t type as a domain. type echoclient_t, domain; # Define echoclient_exec_t as a type of executable # file. type echoclient_exec_t, file_type, exec_type; # This is a macro which will allow a correctly # labeled executable to transition into the # echoclient_t domain from the staff_t domain. domain_auto_trans(staff_t, echoclient_exec_t, echoclient_t) # Designate which roles may enter the echoclient_t # domain. role staff_r types echoclient_t; # This is a macro which allows the domain to use # shared libraries. uses_shlib(echoclient_t); # Provide the permissions required to run the # program when logged in via SSH as staff_t, # allowing diagnostic and error messages to be # written to the user's tty. allow echoclient_t sshd_t:fd use; allow echoclient_t staff_devpts_t:chr_file { getattr read write }; # Network configuration # These are the socket permissions required by the # domain. Note that they are locked down to TCP # sockets. allow echoclient_t echoclient_t:tcp_socket { connect create read shutdown write }; # Allow the program to send and receive TCP messages # to the echo port. In standard policy, the port is # labeled as an inetd_port_t as it is one of a group # of ports managed by inetd. You could modify the # policy in net_contexts to lock this down to one # port if needed. allow echoclient_t inetd_port_t:tcp_socket { recv_msg send_msg }; # Allow only TCP traffic over the intranet interface. allow echoclient_t netif_intranet_t:netif { tcp_recv tcp_send }; # Allow only TCP communication with internal IP # addresses. allow echoclient_t node_internal_t:node { tcp_recvtcp_send };
将以下标记定义添加到 net_contexts 文件
# Label eth0 netifcon eth0 system_u:object_r:netif_intranet_t system_u:object_r:unlabeled_t # Label the internal network. nodecon 10.3.1.0 255.255.255.0 system_u:object_r:node_internal_t
更新 types/network.te 文件
# Define netif_intranet_t as a type of network # interface. type netif_intranet_t, netif_type;
在新文件 file_contexts/program/echoclient.fc 中为可执行文件定义文件上下文
# Default file context for labeling /tmp/echoclient -- system_u:object_r:echoclient_exec_t
编译并加载策略
$ make load
就这样——策略完成了。看起来要做很多事情,但是一旦你熟悉了各种策略文件和所需的策略条目类型,就会变得更容易。使用 audit2allow 等工具也有帮助,它可以获取审计日志拒绝消息并将它们转换为 allow 规则。最好使用高级 GUI 策略工具进行日常策略开发;我们在这里逐步进行,以展示事情是如何运作的。
现在,构建并标记客户端可执行文件
$ make echoclient cc echoclient.c -o echoclient $ restorecon /tmp/echoclient
验证其标记是否正确
$ getfilecon /tmp/echoclient /tmp/echoclient system_u:object_r:echoclient_exec_t
你可以使用ls -Z代替。
让我们看看它是否有效——以 staff_r 角色的 root 身份登录,使用 SSH
$ id -Z root:staff_r:staff_t $ /tmp/echoclient 10.3.1.2 Sending message: 'Hello, cliche' Received message: 'Hello, cliche'
它工作了!
如果需要,您可以向策略添加 auditallow 规则来监视授予的每个权限。
让我们验证一些策略规则是否真的在工作。
1) 尝试与内联网之外的 IP 地址通信。在本地路由地址,这样您就不会意外地将数据包发送到 Internet
$ ip ro add 196.40.74.92 via 10.3.1.2 dev eth0 $ /tmp/echoclient 196.40.74.92
程序获得 TCP 超时,并且在发送数据包时会生成以下审计拒绝消息
avc: denied { tcp_send } for pid=10831 exe=/tmp/echoclient saddr=10.3.1.1 src=32822 daddr=196.40.74.92 dest=7 netif=eth0 scontext=root:staff_r:echoclient_t tcontext=system_u:object_r:node_t tclass=node
正如预期的那样,echoclient_t 域被拒绝访问以将 TCP 数据包传输到 /node_t/ 节点,即默认的通用节点上下文。
2) 尝试通过错误的接口进行通信。通过环回接口路由回显服务器 IP,以便数据包将发送到那里
$ ip ro add 10.3.1.2 via 127.0.0.2 dev lo $ /tmp/echoclient 10.3.1.2 avc: denied { tcp_send } for pid=10828 exe=/tmp/echoclient saddr=10.3.1.1 src=32821 daddr=10.3.1.2 dest=7 netif=lo scontext=root:staff_r:echoclient_t tcontext=system_u:object_r:netif_lo_t tclass=netif
这也工作正常。echoclient_t 域被拒绝访问以通过 netif_lo_t netif 传输数据包。
echoclient 程序以策略中定义的非常小的权限集运行。任何未明确允许的内容都被拒绝。程序中的缺陷、用户错误或恶意用户可能造成的潜在损害将受到此策略的极大限制。
这是一个如何使用 SELinux 策略实现网络安全目标的简单演示。现实世界的策略将需要几个额外的功能,由于空间和清晰度的原因而省略了这些功能,例如使用 ICMP 消息和 DNS 查找的能力。有关详细示例,请参阅您的发行版的策略源包,并尝试一些 GUI 策略工具。
SELinux 很可能实施某种形式的标记网络。这是网络流量本身被标记的情况,通常用于处理机密信息的军事和政府环境中。早期版本的 SELinux 使用 IP 选项来标记数据包,尽管在与上游内核合并之前被删除,因为它需要的挂钩过于侵入性。一种可能的替代方案是将 SELinux 与 IPSec 集成,并标记安全关联 (SA) 而不是数据包。在特定 SA 上到达的数据包将被隐式地标记为 SA 的上下文。此方案的原型已为之前的 Flask 项目实施,它应该可以作为指南。
SELinux 与网络安全组件(如密码学和防火墙)的更通用集成也是未来探索的领域。
James Morris (jmorris@redhat.com) 是一位来自澳大利亚悉尼的内核黑客,目前在波士顿的 Red Hat 工作。他是 SELinux、网络和 Crypto API 的内核维护者;LSM 开发人员,以及 Netfilter 核心团队的荣誉成员。