抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

沙箱技术杂谈

为什么沙箱技术被称为沙箱技术?

在现实生活中,沙箱是一个装满沙子的小箱子,小孩儿们可以在沙箱里面发挥自己的想象力——建沙堡、画画等,而不会将沙子弄得满地都是。

在计算机安全领域,沙箱技术的主要目标是隔离和保护程序,以防止它们对系统或其他应用程序造成不必要的损害或干扰。

沙箱技术的历史

早在上世纪60年代,就已经通过硬件实现了系统和进程代码的隔离。

80年代,通过硬件方法隔离不同进程的内存空间。VAX/VMS操作系统引入了”访问控制列表”的概念,允许管理员对文件和资源进行更细粒度的权限控制。这是隔离和控制访问的重要步骤。

90年代,互联网逐渐开始普及。产生了解释器和被解释的代码之间的隔离,以及Java虚拟机(JVM)等早期沙箱技术。

2000年左右,是针对浏览器的网络攻击最盛之时。

而2010年前后,现代沙箱技术崛起了,主要用于浏览器,让不被信任的代码、数据被放置于一个被隔离的进程中。当被隔离的进程(子进程)想要执行一些需要授权的操作时,需要向父进程(如Firefox)请求,得到允许后方可执行。

沙箱逃逸

chroot()

chroot()系统调用最早于1979年出现在Unix系统,然后出现在BSD系统。这是一种传统沙盒。

chroot('/sandbox')的作用是让调用该函数的进程以及其子进程认为根目录/sandbox,在/sandbox中时,不能cd ../到真正的根目录,因此一定程度上限制了进程对/sandbox外资源的访问。

路径穿越

考虑在根目录运行如下C代码(不完整):

1
2
chroot("/sandbox");
execl("/busybox", "sh", 0); // Busybox在单一的可执行文件中提供了精简的Unix工具集,在这可以理解为运行了一个Linux系统

初看:busybox运行,它认为自己的根目录是/sandbox。但问题是,调用chroot()不会自动更改工作目录,因此busybox的工作目录还在根目录,可能可以借由这个工作目录访问沙箱外的资源。

资源未关闭

仅仅执行chroot(),并不会将先前打开的资源(如文件)关闭(留下文件句柄)。

Linux中有许多后缀为at的系统调用,可以根据目录(的句柄)和相对路径找到文件。以下是一些例子:

1
2
int openat(int dirfd, char *pathname, int flags);
int execveat(int dirfd, char *pathname, char **argv, char **envp, int flags);

如果先前有打开的目录未释放句柄,那么很容易利用句柄访问任意文件。

重复调用chroot()

调用了一次chroot()后,如果没有明确限制,可以再次调用chroot()。考虑以下情况:

位于根目录为/sandbox的沙箱中(已经执行chroot("/sandbox");)。

1
2
3
4
5
6
mkdir springboard	# 在沙箱的根目录中创建目录
chroot springboard # 现在进程认为/sandbox/springboard是它的根目录
# 但是现在工作目录是/sandbox,我们实际上处于进程所认为的根目录外面,因此不受限制
# chdir ../../
# 现在工作目录就是/
# 我们已经到达了真正的根目录

seccomp

seccomp被称作系统调用的防火墙。可以禁用某些系统调用,或者基于参数来禁用。
docker、chrome、firefox等,都依赖于seccomp

seccomp的规则将会被children继承

1
2
3
4
5
6
scmp_filter_ctx ctx;										//seccomp过滤规则的数据结构
ctx = seccomp_init(SCMP_ACT_ALLOW); //初始化seccomp, 允许所有系统调用
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0); //禁止 execve 系统调用
seccomp_load(ctx); //应用规则

execl("/bin/cat", "cat", "/flag", (char *)0); //这个函数会调用 execve,将被 kill

更多使用方法:

1
man seccomp_rule_add

宽松的过滤规则

Linux系统现在有300多种系统调用,且仍然在不断更新,十分复杂。沙箱应用开发者为了功能、性能或方便,可能会制定相对宽松的过滤规则。有些未被允许的系统调用就有可能被用于逃逸。

系统调用混淆

许多64位架构向后兼容了32位。在amd64等架构中,你可以在同一个进程中切换32/64位模式。

不过有趣的是,不同架构(甚至是同一个架构的32/64位)的系统调用号不一样。例如:

exit()在amd64中号码是60,在x86中是1。

在amd64操作系统中,seccomp默认配置的是amd64下允许/禁用的系统调用。如果允许两种模式下的系统调用,那么沙盒很可能顾此失彼。

考虑下面这种情况:

系统调用号 amd64 x86
3 close read()
4 stat() write()
5 fstat() open()

如果开发者在配置规则时,本意是允许close(), stat(), fstat()系统调用,但由于seccomp根据系统调用号进行过滤,当我们使用32位的指令进行系统调用时,我们竟然可以调用open, read, write——这意味着我们可以读取文件的内容并输出到其他文件或标准输出等。

内核漏洞

如果沙盒的seccomp被正确配置,攻击者很发起有用的攻击。但是,用户仍然可以调用在白名单中的系统调用。如果内核中含有漏洞,通过系统调用,攻击者就可能利用这些内核漏洞。

听起来很玄乎,但其实内核与普通软件一样,都是代码,都或多或少存在漏洞,单是2019年一年,就有超过30个Chrome沙盒逃逸,其中大部分利用了内核漏洞。

namespace——现代解决方案

命名空间是 Linux 中可用的功能,用于隔离不同系统资源方面的进程。在Linux中有很多namespace,包括:

  • mnt(挂载点,文件系统)
  • pid(进程)
  • net(网络堆栈)
  • IPC(系统 V IPC)
  • uts(主机名)
  • 用户(UID)

等。使用挂载命名空间、pivot_root可以让用户只能访问原本文件树的一部分。

原有root挂载点未删除

一般会将旧的根目录挂载到沙箱内某个挂载点,然后使用pivot_root将根目录换成新的“根目录”,然后再将该挂载点和目录删除。如果忘记删除原有挂载点,则沙箱形同虚设。

共享文件系统

如果沙箱和宿主共用文件系统(沙箱对文件系统有写权限),那么沙箱用户就可以利用自己的root权限来在沙箱外提取。
例如,在沙箱内:

1
chmod 4755 /bin/cat

那么在沙箱外的任何用户就都可以以root身份运行cat,这意味着可以读取任何文件。

先前打开的资源未关闭

chroot()中讨论的一致。

setns()和/proc

setns():

1
int setns(int fd, int nstype);

fd参数是下列两者其中之一:

• 指向/proc/pid/ns/目录中的一个链接(或绑定挂载到此类链接)的文件描述符;

• 一个进程文件的句柄

nstype视情况而定。

setns()系统调用可以将当前进程的命名空间切换成其他进程的命名空间。

在bash中,可以:

1
2
PID=149
nsenter --mount=/proc/$PID/ns/mnt cat /flag

这样当前bash就会拥有和PID为149的进程一样的挂载命名空间。假如这个进程是沙箱外的,那么当前bash就有了沙箱外的挂载命名空间,意味着沙箱内用户可以访问完整的文件树。

也就是说,将原有的/proc挂载到沙箱中,且沙箱外有其他进程未关闭的情况下,是非常危险的!

Docker

Docker容器 vs 沙箱

Docker容器和沙箱有许多相似之处,例如:

  • 环境隔离

    两者都与宿主系统产生了一定的隔离,起到了限制和保护作用

  • 轻量

    相比于虚拟机,两者都称得上轻量

但两者是截然不同的技术:

  • 隔离级别:

    Docker容器提供了一种相对较高级别的隔离,包括文件系统隔离、进程隔离、网络隔离等。容器之间通常是相互隔离的,但它们仍然在同一个操作系统内核上运行。沙箱通常提供更加细粒度的隔离,通常是为了限制单个应用程序或代码的权限。沙箱环境可以是单进程的,不涉及多个容器或应用程序的协同工作。

  • 用途:

    Docker容器是用于构建、打包和运行应用程序的独立、可移植的环境。它们旨在在不同的环境中一致地运行应用程序,包括开发、测试和生产环境。Docker容器通常包括应用程序及其依赖项,并提供了隔离、版本控制和自动化部署的能力。沙箱是一种安全机制,用于隔离和限制运行在其中的代码或程序的能力。沙箱旨在提供一种受限制的执行环境,以防止应用程序或代码对系统或其他应用程序造成损害。它通常用于执行不受信任的代码或对应用程序进行测试,以减少潜在的风险。

  • 隔离技术:

    Docker容器使用容器化技术,如Docker引擎,通过Linux命名空间、控制组等技术提供隔离和资源管理。沙箱可以使用各种技术,包括操作系统级别的虚拟化、chroot、Seccomp、AppArmor等,具体取决于实现。

Docker安全

除了在沙箱技术中提到的namespace, seccomp等,Docker还使用capabilities, control groups等来Linux提供的功能来提升安全性。

  1. cgroups:
    • 资源隔离:Docker使用 cgroups 来隔离容器的资源使用,包括CPU、内存、磁盘I/O等。每个容器都可以分配一定的资源配额,以确保它们不会互相干扰或抢占主机上的资源。
    • 资源限制:cgroups 允许设置容器的资源限制,例如限制 CPU 使用率、内存使用量等。这有助于防止容器滥用主机上的资源,提高整个系统的稳定性。
    • 资源监控:通过 cgroups,你可以监视容器的资源使用情况,以便进行性能调整和资源规划。
  2. capabilities:
    • 最小权限原则:Docker 使用 Linux 的 capabilities 功能来确保容器中的进程以最小权限原则运行。capabilities 允许我们将权限分配给进程,而不需要完全的 root 权限。这样可以减小潜在的攻击面。
    • 降低特权:Docker 默认情况下会剥夺容器中的进程一些敏感的权限,如修改主机的网络配置或访问主机的设备。这有助于降低容器中运行的进程对主机的潜在威胁。

值得一提的是,如果主机 /proc 目录被挂载在 docker 容器中,而且容器的capabilities配置了 CAP_SYS_ADMIN (很高的权限,例如可以挂载文件系统),那么我们能够很轻松的从容器中逃逸。

评论