阐述 SELinux 在 Apache 中的作用
这是续集
请使用SELinux。使用吧。由于在2016年的Linux Advent Calendar 2016上发布了这个消息后,事情变得很大,所以我决定写续篇。
这次的主题
我们将在启用SELinux环境和禁用SELinux环境下,使用Apache运行CGI来观察嵌入后门的行为差异。
环境
操作系统: CentOS 7.3.1611
SELinux: 目标模式
使用 CGI
为了节省麻烦,我使用 ncat。
当 CGI 执行时,它会打开 0.0.0.0:9000 并等待连接。
对于 CentOS,由于 Apache 的工作进程在 apache 用户下运行,因此无法打开 Well-known Ports,所以我选择了一个适当的端口。
同时,我将其注册为 HTTP 端口。(因为如果使用完全不相关的端口,显然会被阻止,这样就不有趣了)
# semanage port -l | grep 9000
http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000
#!/bin/sh
echo "content-type: text/plain"
echo ""
nc 0.0.0.0 -l 9000 &
让我们试试看
如果禁用了SELinux或者设置为Permissive模式的情况下
首先,访问CGI。由于没有返回内容,我们将其阻塞。
$ curl localhost/cgi-bin/nc.sh
<<< ここでブロックする >>>
然后,nc会打开并等待9000/tcp端口。
# ss -atnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:111 *:* users:(("systemd",pid=1,fd=40))
LISTEN 0 5 192.168.122.1:53 *:* users:(("dnsmasq",pid=1343,fd=6))
LISTEN 0 128 *:22 *:* users:(("sshd",pid=1166,fd=3))
LISTEN 0 128 127.0.0.1:631 *:* users:(("cupsd",pid=1144,fd=12))
LISTEN 0 100 127.0.0.1:25 *:* users:(("master",pid=1291,fd=13))
LISTEN 0 10 *:9000 *:* users:(("nc",pid=2082,fd=3))
ESTAB 0 0 192.168.0.111:22 192.168.0.2:55270 users:(("sshd",pid=1953,fd=3),("sshd",pid=1949,fd=3))
ESTAB 0 52 192.168.0.111:22 192.168.0.2:54375 users:(("sshd",pid=1452,fd=3),("sshd",pid=1447,fd=3))
LISTEN 0 128 :::111 :::* users:(("systemd",pid=1,fd=39))
LISTEN 0 128 :::80 :::* users:(("httpd",pid=1902,fd=4),("httpd",pid=1901,fd=4),("httpd",pid=1900,fd=4),("httpd",pid=1873,fd=4),("httpd",pid=1870,fd=4),("httpd",pid=1869,fd=4),("httpd",pid=1868,fd=4),("httpd",pid=1867,fd=4),("httpd",pid=1866,fd=4),("httpd",pid=1865,fd=4))
LISTEN 0 128 :::22 :::* users:(("sshd",pid=1166,fd=4))
LISTEN 0 128 ::1:631 :::* users:(("cupsd",pid=1144,fd=11))
LISTEN 0 100 ::1:25 :::* users:(("master",pid=1291,fd=14))
ESTAB 0 0 ::1:80 ::1:34120 users:(("httpd",pid=1869,fd=9))
ESTAB 0 0 ::1:34120 ::1:80 users:(("curl",pid=2079,fd=3))
我会从另一个终端向这里发送一条消息。
$ nc localhost 9000
hogehoge
foobar
foobarbaz
<<<Ctrl-D>>>
当我在 nc 中发送文字时,之前被封锁(通过 curl)的终端会显示终端 3 发送的消息。
$ curl localhost/cgi-bin/nc.sh
hogehoge
foobar
foobarbaz
发生了什么事情?
当执行CGI时,nc会打开0.0.0.0:9000并等待。从其他终端发送任意字符串到这个地址,可以将字符串发送到客户端。
在这个例子中,我们只是发送了一个字符串,但是真正的问题是
-
- 任意のコマンドを実行できてしまう
- 場合によっては任意のポートを開くことができてしまう
这是指的。 (Zhè shì zhǐ de.)
让我们将SELinux切换到Enforce模式。
首先要访问 CGI。
这次不会返回任何内容,也不会阻塞。
$ curl localhost/cgi-bin/nc.sh
$
nc 是什么?
# ss -atnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:111 *:* users:(("systemd",pid=1,fd=40))
LISTEN 0 5 192.168.122.1:53 *:* users:(("dnsmasq",pid=1343,fd=6))
LISTEN 0 128 *:22 *:* users:(("sshd",pid=1166,fd=3))
LISTEN 0 128 127.0.0.1:631 *:* users:(("cupsd",pid=1144,fd=12))
LISTEN 0 100 127.0.0.1:25 *:* users:(("master",pid=1291,fd=13))
ESTAB 0 0 192.168.0.111:22 192.168.0.2:55270 users:(("sshd",pid=1953,fd=3),("sshd",pid=1949,fd=3))
ESTAB 0 0 192.168.0.111:22 192.168.0.2:54375 users:(("sshd",pid=1452,fd=3),("sshd",pid=1447,fd=3))
LISTEN 0 128 :::111 :::* users:(("systemd",pid=1,fd=39))
LISTEN 0 128 :::80 :::* users:(("httpd",pid=1902,fd=4),("httpd",pid=1901,fd=4),("httpd",pid=1900,fd=4),("httpd",pid=1873,fd=4),("httpd",pid=1870,fd=4),("httpd",pid=1869,fd=4),("httpd",pid=1868,fd=4),("httpd",pid=1867,fd=4),("httpd",pid=1866,fd=4),("httpd",pid=1865,fd=4))
LISTEN 0 128 :::22 :::* users:(("sshd",pid=1166,fd=4))
LISTEN 0 128 ::1:631 :::* users:(("cupsd",pid=1144,fd=11))
LISTEN 0 100 ::1:25 :::* users:(("master",pid=1291,fd=14))
# ss -atnp | grep nc
我不在。
让我们来看一下 Apache 的日志。
[Sun Jan 08 11:30:30.374900 2017] [cgi:error] [pid 1867] [client ::1:34144] AH01215: Ncat: bind to 0.0.0.0:9000: Permission denied. QUITTING.
nc 被拒绝许可。接下来,我们来查看审计日志。
# ausearch -m avc
...(略)...
time->Sun Jan 8 11:30:30 2017
type=SYSCALL msg=audit(1483842630.373:431): arch=c000003e syscall=49 success=no exit=-13 a0=3 a1=658c60 a2=80 a3=7ffddcbf3e10 items=0 ppid=1 pid=31418 auid=4294967295 uid=48 gid=48 euid=48 suid=48 fsuid=48 egid=48 sgid=48 fsgid=48 tty=(none) ses=4294967295 comm="nc" exe="/usr/bin/ncat" subj=system_u:system_r:httpd_sys_script_t:s0 key=(null)
type=AVC msg=audit(1483842630.373:431): avc: denied { name_bind } for pid=31418 comm="nc" src=9000 scontext=system_u:system_r:httpd_sys_script_t:s0 tcontext=system_u:object_r:http_port_t:s0 tclass=tcp_socket
因为一个名为system_u的进程试图在http_port_t的9000/tcp上绑定nc,所以被拒绝了,并在审计日志中记录下来。
为什么?
首先我们来看一下执行的CGI上下文。
CGI的执行需要httpd_sys_script_exec_t,正如之前所说,CGI本身可以正常运行,所以文件上下文也正确附加。
审计日志中也有这个上下文的违规记录,所以没有错误。
# ls -Z /var/www/cgi-bin/
-rwxr-xr-x. root root unconfined_u:object_r:httpd_sys_script_exec_t:s0 nc.sh
接下来我们来看一下 SELinux 的设置。
为了将端口绑定并打开,必须允许 name_bind。
我们来查看httpd_sys_script_exec_t是否可以在name_bind权限下绑定http_port_t。
# sesearch --allow -s httpd_sys_script_t | grep http_port_t
#
没有符合条件的结果。
那么,能进行name_bind的端口是什么样的呢?
# sesearch --allow -s httpd_sys_script_t | grep name_bind
allow httpd_script_type ephemeral_port_t : udp_socket name_bind ;
allow nsswitch_domain port_t : udp_socket name_bind ;
allow httpd_script_type port_t : udp_socket name_bind ;
allow nsswitch_domain port_t : tcp_socket name_bind ;
allow nsswitch_domain ephemeral_port_t : udp_socket name_bind ;
allow httpd_script_type port_t : tcp_socket name_bind ;
allow nsswitch_domain unreserved_port_t : udp_socket name_bind ;
allow nsswitch_domain ephemeral_port_t : tcp_socket name_bind ;
allow httpd_script_type unreserved_port_t : udp_socket name_bind ;
allow httpd_script_type ephemeral_port_t : tcp_socket name_bind ;
allow nsswitch_domain unreserved_port_t : tcp_socket name_bind ;
allow httpd_script_type unreserved_port_t : tcp_socket name_bind ;
比如作为客户端,可以打开临时端口(32768-61000)。
无法自行打开用于服务的端口。
因此,在正确配置 SELinux 的情况下,无法非法地打开端口。
顺便说一下,Apache 是如何打开 WKP 的 80/tcp 端口的呢…
# ps xafZ | grep httpd | grep -v grep
system_u:system_r:httpd_t:s0 1865 ? Ss 0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0 1866 ? S 0:00 \_ /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0 1867 ? S 0:00 \_ /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0 1868 ? S 0:00 \_ /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0 1869 ? S 0:00 \_ /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0 1870 ? S 0:00 \_ /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0 1873 ? S 0:00 \_ /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0 1900 ? S 0:00 \_ /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0 1901 ? S 0:00 \_ /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0 1902 ? S 0:00 \_ /usr/sbin/httpd -DFOREGROUND
# sesearch --allow -s httpd_t -t http_port_t
Found 11 semantic av rules:
allow httpd_t port_type : tcp_socket { recv_msg send_msg } ;
allow httpd_t port_type : udp_socket { recv_msg send_msg } ;
allow httpd_t http_port_t : udp_socket name_bind ;
allow httpd_t http_port_t : tcp_socket name_bind ;
allow httpd_t port_type : tcp_socket name_connect ;
allow nsswitch_domain port_type : udp_socket recv_msg ;
allow nsswitch_domain port_type : udp_socket send_msg ;
allow nsswitch_domain port_type : tcp_socket { recv_msg send_msg } ;
allow httpd_t http_port_t : tcp_socket name_connect ;
allow httpd_t http_port_t : tcp_socket name_connect ;
allow nsswitch_domain reserved_port_type : tcp_socket name_connect ;
如上所述,httpd_t上下文允许打开http_port_t端口。
最后
本次我们故意执行了未在策略中定义的非法行为,并观察了结果。
当然,如果需要这样的操作,可以自行定义端口并为SELinux定义策略,以确保正确运行。
我们下次写一下步骤吧…