哈佛大学使用 SELinux 沙箱构建 PaaS 平台

暂无读者喜欢这篇文章。
Freer than free, opener than open: The fight for the learning management systems

Opensource.com

运行学生提交的程序对任何大学计算机科学系来说都是一项安全挑战。当哈佛大学就他们在 Fedora 17 上使用“沙箱”工具所做的一些工作与我联系时,我们认为这是一个很好的机会,可以了解他们如何能够更好地利用它,并与社区分享我们的发现。 

在很多方面,哈佛大学正在建立一个简单的 PaaS(平台即服务)。我们讨论了像 OpenShift 和安全 Linux 容器这样的工具,但当务之急是,一旦他们开始提供“C 语言入门”在线课程,学生们将程序上传到哈佛大学的 Web 服务器,这些程序将被编译和测试。而且,不出所料,他们担心学生们的“C”应用程序可能会尝试做什么。

一种选择是为每个用户设置一个单独的帐户,使用 UID 分离。他们决定不这样做,因为无法保证他们会知道学生是谁,为每个学生执行 useradd 会很困难,而且学生可能只使用系统一次。我相信课程最终将向所有人开放,所有学生都将以用户 jhavard(以约翰·哈佛的名字命名)身份登录。 

当我到达那里时,我很惊讶哈佛大学使用的是 sandbox 而不是 sandbox -X,因为大部分时间我都在和人们谈论沙箱的桌面版本。他们使用的是服务器(非 GUI)版本。

当学生将程序上传到他们的网站时,脚本会动态生成一个随机的 homedirtmpdir,然后执行 sandbox 命令来创建一个隔离环境,在该环境中运行学生的“测试”程序。sandbox 实用程序利用命名空间(容器)将新创建的 homedirtmpdir 挂载到 $HOME/tmp 上。然后,它使用 sandbox_t 类型和一个随机生成的 MCS 标签运行学生的“测试”程序。

SELinux 内核阻止 sandbox_t 类型在服务器上执行特权应用程序,从而控制学生进程被允许执行的操作。sandbox_t 只允许修改其 homedirtempdir 中的内容,以及读取/执行系统文件——它不允许执行任何 setuid 应用程序。SELinux 使用 MCS 标签来保证任何学生的进程都不会干扰任何其他学生的进程或文件。 

然后编译并执行学生的代码。这些工具捕获 stdoutstderr 以供教授稍后审查,最后,服务器被编程为从学生提交的所有内容中删除脚本。 

哈佛大学在使用默认的 sandbox_t 类型时遇到的一个问题是,教授会给学生布置包含网络代码的程序,并要求完全的 TCP/UDP 网络访问,而 sandbox_t 默认情况下会拒绝这种访问。哈佛大学通过启用网络并使用 iptables 来约束学生应用程序可以连接的网络端口和主机来解决这个问题。这样,如果作业的特点是连接到像 people.redhat.com/dwalsh 这样的站点来下载文档,教授就可以允许学生的程序访问 people.redhat.com,同时阻止访问所有其他网站。

扩展沙箱策略

为了测试策略,我们使用了 runcon 而不是 sandbox,因为它更容易测试。

$ runcon -t sandbox_t nc localhost 80
p_ssl_init: Failed to seed OpenSSL PRNG (RAND_status returned false)

我解释了一些关于编写 SELinux 策略的知识,然后我们开始扩展标准的 SELinux 策略。

# cat mysandbox.te

policy_module(mysandbox,1.0)

gen_require(`
type sandbox_t;
')
# extend sandbox to be allowed to connect to TCP and UDP sockets
corenet_tcp_connect_all_ports(sandbox_t)
corenet_udp_sendrecv_all_ports(sandbox_t)
sysnet_dns_name_resolve(sandbox_t)
dev_read_urand(sandbox_t)
我们在这里使用策略模块类型强制 (te) 文件来扩展 sandbox_t
首先,我们需要指定此模块需要 sandbox_t。其次,我们使用 corenet 接口允许 sandbox_t 连接到任何 TCP 端口,并从任何 UDP 端口发送/接收。我们还启用了 sysnet_dns_name_resolve 接口,该接口实际上允许使用 DNS 解析器,并自动使用其他策略来允许我们创建 TCP 和 UDP 套接字。在测试期间,我们注意到 shell 使用 /dev/urandom 进行连接,因此我们也允许了这一点。 

 

我们的策略文件完成后,我们使用提供的 make 文件对其进行编译

# make -f /usr/share/selinux/devel/Makefile mysandbox.pp

最后,我们安装了编译后的策略包,并重新测试以查看我们是否可以连接到网络

# semodule -i mysandbox.pp
# runcon -t sandbox_t nc localhost 80

现在 sandbox_t 允许连接到端口 80。

然后,哈佛大学需要在他们的所有测试服务器上分发和安装编译后的策略文件 (mysandbox.pp),方法是运行相同的命令。PuppetChef 是自动化此过程的优秀工具。

多域

哈佛大学还有另一个想法——他们想允许教授上传他们自己的测试套件以及学生的代码。然而,当前的设置存在一个问题

学生的代码可能会影响教授的测试套件!

如果我可以修改教授的测试以始终返回成功,我可能会提高我的成绩。

我们决定编写一个新的策略,系统上的进程将以两种不同的类型运行:sandbox_staff_tsandbox_t。我们还创建了两种文件类型,sandbox_staff_file_tsandbox_file_t$HOME 目录及其所有内容都被标记为 sandbox_staff_file_t$HOME 现在包含两个目录,staffstudent。student 目录的内容被标记为 sandbox_file_t。staff 测试存储在 staff 目录中,标记为 sandbox_staff_file_t。进程类型的规则是 sandbox_t 可以管理 sandbox_file_t 中的所有内容,而 sandbox_staff_t 可以管理 sandbox_staff_file_tsandbox_file_t 中的所有内容。

sandbox_staff_t 进程需要能够执行 student 目录中的脚本,但它不能以 sandbox_staff_t 身份执行,否则这将允许学生的脚本攻击 staff 目录。我们需要一个过渡规则,即当 sandbox_staff_t 进程执行标记为 sandbox_file_t 的文件时,它会过渡到 sandbox_t。我们还希望允许 sandbox_staff_tsandbox_t 进程发送信号,即“kill”,并允许它跟踪它们。

这是我们为此编写的策略

policy_module(mysandbox,2.0)

gen_require(`

type sandbox_t;
type sandbox_file_t;
attribute sandbox_file_type;
')
# extend sandbox to be allowed to connect to TCP and
# UDP sockets
corenet_tcp_connect_all_ports(sandbox_t)
corenet_udp_sendrecv_all_ports(sandbox_t)
sysnet_dns_name_resolve(sandbox_t)
dev_read_urand(sandbox_t)
# Create new sandbox domain sandbox_staff_t, this
# interface also adds the rules to allow sandbox_staff_t to
# manage all content labeled sandbox_file_t.
sandbox_domain_template(sandbox_staff)
# Create a type for all staff content
type sandbox_staff_file_t, sandbox_file_type;
files_type(sandbox_staff_file_t)
# Rules to allow sandbox_staff_t, to manage content in
# sandbox_staff_file_t.
manage_files_pattern(sandbox_staff_t, sandbox_staff_file_t, sandbox_staff_file_t)
manage_dirs_pattern(sandbox_staff_t, sandbox_staff_file_t, sandbox_staff_file_t)
manage_sock_files_pattern(sandbox_staff_t, sandbox_staff_file_t, sandbox_staff_file_t)
manage_fifo_files_pattern(sandbox_staff_t, sandbox_staff_file_t, sandbox_staff_file_t)
manage_lnk_files_pattern(sandbox_staff_t, sandbox_staff_file_t, sandbox_staff_file_t)
 
# Now we write rules to control what happens when
# sandbox_staff_t executes a program,
 
# If the process executes a program labeled
# sandbox_staff_file_t it will run as sandbox_staff_t
can_exec(sandbox_staff_t, sandbox_staff_file_t)
 
# If the sandbox_staff_t process runs a file labeled
# sandbox_file_t, it will transition to sandbox_t.
domtrans_pattern(sandbox_staff_t, sandbox_file_t,sandbox_t)
 
# Allow the sandbox_staff_t process to control the
# sandbox_t process.
allow sandbox_staff_t sandbox_t:process { signal_perms ptrace };
ps_process_pattern(sandbox_staff_t, sandbox_t)
 
 
# Allow the sandbox_t process to search through the
# sandbox_staff_t directories
allow sandbox_t sandbox_staff_file_t:dir search_dir_perms;
 
# Since the tests are going to be executing shells, shell
# programs seem to want to read /proc files
kernel_read_system_state(sandbox_t)
kernel_read_system_state(sandbox_staff_t)

编译并安装

# make -f /usr/share/selinux/devel/Makefile mysandbox.pp
# semodule -i mysandbox.pp

测试以确保可以以 sandbox_staff_t 身份运行

# runcon -t sandbox_staff_t id -Z
staff_u:unconfined_r:sandbox_staff_t:s0-s0:c0.c1023

很好,它工作了。现在,设置一个测试环境。

设置测试环境

首先,设置一个名为“class”的主目录,其中包含子目录 staffstudent,并编写测试脚本,这些脚本输出脚本运行时的 SELinux 上下文和“你好,世界”。

$ mkdir -p class/staff class/student
$ cat > class/staff/test.sh
#!/bin/sh
id -Z
echo "Hello world staff"
$ cat > class/student/test.sh
#!/bin/sh
id -Z
echo "Hello world student"
$ chmod +x class/staff/test.sh class/student/test.sh

设置目录的上下文,默认所有目录为 sandbox_staff_file_tclass/studentsandbox_file_t

$ chcon -R -t sandbox_staff_file_t class
$ chcon -R -t sandbox_file_t class/student

使用 find 检查标签

$ find class -printf "%p\t\t%Z\n"

class  staff_u:object_r:sandbox_staff_file_t:s0
class/student  staff_u:object_r:sandbox_file_t:s0
class/student/test.sh  staff_u:object_r:sandbox_file_t:s0
class/staff  staff_u:object_r:sandbox_staff_file_t:s0
class/staff/test.sh  staff_u:object_r:sandbox_staff_file_t:s0

开始测试。

$ id -Z
staff_u:staff_r:staff_t:s0-s0:c0.c1023
$ runcon -t sandbox_t id -Z
staff_u:unconfined_r:sandbox_t:s0-s0:c0.c1023
$ runcon -t sandbox_t touch class/bad
touch: 无法创建 'class/bad': 权限被拒绝
$ runcon -t sandbox_t touch class/staff/bad
touch: 无法创建 'class/staff/bad': 权限被拒绝

优秀!这表明学生进程无法写入 class 或 staff 目录。如果我们允许学生代码写入这些目录,它可能会创建一个 .bashrc 或 profile 文件,并欺骗 sandbox_staff_t 进程做一些坏事。

$ runcon -t sandbox_t touch class/student/good

这表明学生的代码可以写入他们自己的目录,这正是我们想要的。

验证 sandbox_staff_t 进程可以写入所有目录

$ runcon -t sandbox_staff_t touch class/staff/good
$ runcon -t sandbox_staff_t touch class/student/good

通过此测试后,现在是时候确保上下文转换正常工作了。

# 测试以确保当运行学生代码时,即标记为 sandbox_file_t 的程序,sandbox_staff_t 进程将转换到正确的上下文。

$ runcon -t sandbox_staff_t class/staff/test.sh
staff_u:unconfined_r:sandbox_staff_t:s0-s0:c0.c1023
"Hello world staff"
$ runcon -t sandbox_staff_t class/student/test.sh
staff_u:unconfined_r:sandbox_staff_t:s0-s0:c0.c1023
"Hello world student"

糟糕,这看起来不对劲!为什么进程没有以 sandbox_t 身份运行?看起来转换失败了。

策略表明,当 sandbox_staff_t 进程运行 sandbox_file_t 时,它应该转换为 sandbox_t。相反,我们看到进程以 sandbox_staff_t 身份运行,因此我们知道有些东西坏了。让我解释一下这里真正发生了什么。

当执行 runconsandbox 命令时,这些命令会告诉内核将它们执行的下一个进程标记为指定的类型。在这种情况下,runcon 告诉内核以 sandbox_staff_t 身份运行 class/student/test/sh

为了使转换按我们期望的方式工作,我们需要以 sandbox_staff_t 身份执行一个程序 (/bin/sh),然后允许该进程执行 class/student/test.sh

$ runcon -t sandbox_staff_t sh -c class/student/test.sh
staff_u:unconfined_r:sandbox_t:s0-s0:c0.c1023
"Hello world student"

这表明进程正在以 sandbox_t 身份运行,这意味着我们的策略工作正常。哈佛大学现在可以使用如下命令测试沙箱

# sandbox -t sandbox_staff_t -h class staff/test.sh

注意:监控 audit.log 以查看学生在做什么可能会让人望而生畏。每天,都需要有人查看 /var/log/audit/audit.log 中的 AVC 消息,这是我在 OpenShift 上所做的事情之一。

很容易被学生探索系统时生成的 AVC 数量压倒。我看到许多用户在做诸如 find /、rpm -q、su、sudo、passwd 之类的事情——其中大多数我认为是无害的,因为这只是用户在探测系统。说实话,如果我在系统上获得了一个帐户,我可能也会这样做。但是我们发现,当我们删除普通用户执行这些程序的能力后,噪音水平大大降低了。

我建议您删除以下程序的“其他”执行权限标志:rpmyum、debuginfo-installtracerouteiptablesiptables-multidmesgtcpdumpmtripsusudodenyhostschshchfn。 还要将 applications 组修改为 wheel 组,并将任何管理员添加到 wheel 组。

例如

# chmod o-x /usr/bin/rpm

# chgrp wheel /usr/bin/rpm

# ls -l /usr/bin/rpm
-rwxr-xr--. 1 root wheel 27280 Jul 21 22:04 /usr/bin/rpm

# grep wheel /etc/group
wheel:x:10:dwalsh

此更改将阻止学生帐户执行这些命令并触发您可能不关心的 AVC。

User profile image.
丹尼尔·沃尔什在计算机安全领域工作了近 30 年。丹于 2001 年 8 月加入红帽公司。

3 条评论

非常有趣的帖子。
很高兴看到如此专注和实用的 SELinux 沙箱示例。

感谢分享!

好文章,一如既往

为什么不直接使用一次性虚拟机?

© . All rights reserved.