我想知道两点:首先,什么是沙盒?是操作系统系统调用的陷阱,然后再决定是否允许它通过?一开始是如何实施的?是否会通过 SSDT(内核级别)中的挂钩?
沙盒是如何实现的?
好吧,这个答案结束了相当长的时间,但沙盒是一个巨大的话题。在最基本的情况下,沙盒是一种技术,可以最大限度地减少程序在恶意或故障情况下对其余系统的影响。这可以用于测试或增强系统的安全性。人们可能想要使用沙箱的原因也各不相同,在某些情况下它甚至与安全性无关,例如在 OpenBSD 的systrace的情况下。沙盒的主要用途是:
- 程序测试以检测损坏的包,尤其是在构建期间。
- 恶意软件分析以了解恶意软件的行为。
- 保护不受信任或不安全的应用程序,以尽量减少它们可能造成的损害。
有许多沙盒技术,都有不同的威胁模型。有些可能只是通过限制可以使用的 API 来减少攻击面,而另一些则使用类似于Bell-LaPadula或Biba的正式模型来定义访问控制。我将描述一些流行的沙盒技术,主要用于 Linux,但我也会涉及其他操作系统。
赛康
Seccomp是一种 Linux 安全功能,可减少内核攻击面。从技术上讲,它是一个系统调用过滤器,而不是沙箱,但通常用于扩充沙箱。seccomp 过滤器有两种类型,称为模式 1(严格模式)和模式 2(eBPF 模式)。
模式一
Seccomp 模式 1 是最严格、最原始的模式。当程序启用模式 1 seccomp 时,它仅限于使用四个硬编码的系统调用:read()
、、、write()
和。任何需要的文件描述符都必须在执行 seccomp 之前创建。在违规的情况下,违规过程以 终止。exit()
rt_sigreturn()
SIGKILL
取自我在另一个 StackExchange 站点上写的另一个答案,该示例程序可以安全地执行以字节码返回 42 的函数:
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <linux/seccomp.h>
/* "mov al,42; ret" aka "return 42" */
static const unsigned char code[] = "\xb0\x2a\xc3";
void main(void)
{
int fd[2], ret;
/* spawn child process, connected by a pipe */
pipe(fd);
if (fork() == 0) {
close(fd[0]);
/* enter mode 1 seccomp and execute untrusted bytecode */
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
ret = (*(uint8_t(*)())code)();
/* send result over pipe, and exit */
write(fd[1], &ret, sizeof(ret));
syscall(SYS_exit, 0);
} else {
close(fd[1]);
/* read the result from the pipe, and print it */
read(fd[0], &ret, sizeof(ret));
printf("untrusted bytecode returned %d\n", ret);
}
}
模式二
模式 2 seccomp,也称为 seccomp-bpf,涉及将用户空间创建的策略发送到内核,定义允许哪些系统调用,这些系统调用允许哪些参数,以及在违反系统调用的情况下应采取什么措施。过滤器以eBPF字节码的形式出现,这是一种在内核中解释并用于实现过滤器的特殊类型指令集。例如,这用于Linux 上的Chrome/Chromium和OpenSSH沙箱。
一个使用 seccomp-bpf 打印当前 PID 的简单程序:
#include <seccomp.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
void main(void)
{
/* initialize the libseccomp context */
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
/* allow exiting */
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
/* allow getting the current pid */
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getpid), 0);
/* allow changing data segment size, as required by glibc */
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0);
/* allow writing up to 512 bytes to fd 1 */
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 2,
SCMP_A0(SCMP_CMP_EQ, 1),
SCMP_A2(SCMP_CMP_LE, 512));
/* if writing to any other fd, return -EBADF */
seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EBADF), SCMP_SYS(write), 1,
SCMP_A0(SCMP_CMP_NE, 1));
/* load and enforce the filters */
seccomp_load(ctx);
seccomp_release(ctx);
printf("this process is %d\n", getpid());
}
由于Linux 系统调用 ABI将参数保存在通用寄存器中,因此 seccomp 仅验证这些寄存器。这在某些情况下很好,例如当参数是标志的按位或列表时,但在参数是指向内存的指针的情况下,过滤将不起作用。这样做的原因是指针只引用内存,所以验证指针只确保指针本身被允许,而不是它所引用的内存没有改变。这意味着不可能为系统调用可靠地过滤某些参数,例如open()
(其中路径是指向内存中以空字符结尾的字符串的指针)。要过滤路径和类似对象,必须使用强制访问控制或其他基于 LSM 的框架。
保证
OpenBSD 添加了一个系统调用过滤器,类似于(但比粗粒度)seccomp,称为承诺(以前是tame)。Pledge 是一个系统调用,应用程序可以选择加入,本质上是“承诺”他们将限制对各种内核接口的使用。无论应用程序多么恳求,一旦限制到位,内核也不会撤销限制,即使它改变主意。
Pledge 允许应用程序做出承诺,这是一组将被允许的操作,而所有其他操作都将被拒绝。从本质上讲,它承诺只使用它事先明确请求的功能。一些(非详尽的)示例:
- 该标准输入输出承诺允许像关闭文件描述符或内存管理的基本功能。
- 该rpath的承诺允许,可以用来读取文件系统的系统调用。
- 该wpath承诺允许可用于写入文件系统的系统调用。
- 该tmppath承诺允许,可以读取/写入的系统调用,但只有在
/tmp
。 - 该ID承诺允许用于更改凭证,如系统调用
setuid()
。
虽然质押比 seccomp 更粗粒度,但由于这个原因,它更易于使用和维护。正因为如此,OpenBSD 已经发展到向其广泛的基础应用程序添加承诺支持,从安全敏感的sshd
事物到cat
. 这种“默认安全”架构最终极大地提高了整个系统的安全性,即使个别承诺是粗粒度的并且不是特别灵活。
根
chroot是 *nix 的一项功能,它允许将新路径设置为给定程序的根目录,强制它将所有内容视为相对于该路径的内容。这通常不用于安全性,因为特权程序通常可以逃脱 chroot,并且因为它不隔离 IPC 或网络,甚至允许非特权进程进行恶作剧,例如杀死其他进程。一触即发,它可用于增强其他安全技术。它对于防止应用程序造成意外损坏以及为遗留软件提供它所期望的文件系统视图非常有用。
bash
例如,Chrooting将涉及将它需要的任何可执行文件和库放入新目录,并运行chroot
实用程序(它本身只是调用同名的系统调用):
host ~ # ldd /bin/bash
linux-vdso.so.1 (0x0000036b3fb5a000)
libreadline.so.6 => /lib64/libreadline.so.6 (0x0000036b3f6e5000)
libncurses.so.6 => /lib64/libncurses.so.6 (0x0000036b3f47e000)
libc.so.6 => /lib64/libc.so.6 (0x0000036b3f0bc000)
/lib64/ld-linux-x86-64.so.2 (0x0000036b3f938000)
host ~ # ldd /bin/ls
linux-vdso.so.1 (0x000003a093481000)
libc.so.6 => /lib64/libc.so.6 (0x000003a092e9d000)
/lib64/ld-linux-x86-64.so.2 (0x000003a09325f000)
host ~ # mkdir -p newroot/{lib64,bin}
host ~ # cp -aL /lib64/{libreadline,libncurses,libc}.so.6 newroot/lib64
host ~ # cp -aL /lib64/ld-linux-x86-64.so.2 newroot/lib64
host ~ # cp -a /bin/{bash,ls} newroot/bin
host ~ # pwd
/root
host ~ # chroot newroot /bin/bash
bash-4.3# pwd
/
bash-4.3# ls
bin lib64
bash-4.3# ls /bin
bash ls
bash-4.3# id
bash: id: command not found
只有具有该CAP_SYS_CHROOT
能力的进程才能进入 chroot。这对于防止恶意程序在其控制的目录中创建自己的副本/etc/passwd
并使用 setuid 程序(su
如
命名空间
在 Linux 上,命名空间用于隔离系统资源,让命名空间程序对其拥有的资源有不同的理解。这通常用于实现容器。从namespaces(7)
手册页:
命名空间将全局系统资源包装在抽象中,使命名空间内的进程看起来拥有自己的全局资源隔离实例。对全局资源的更改对属于命名空间成员的其他进程可见,但对其他进程不可见。
目前 Linux 下支持 7 个命名空间:
- cgroup - Cgroup 根目录
- IPC - System V IPC 和 POSIX 消息队列
- 网络 - 网络接口、堆栈、端口等
- Mount - 挂载点,功能类似于 chroot
- PID - 进程 ID
- 用户- 用户和组 ID
- UTS - 主机名和域名
unshare
使用该实用程序的 PID 命名空间示例:
host ~ # echo $$
25688
host ~ # unshare --fork --pid
host ~ # echo $$
1
host ~ # logout
host ~ # echo $$
25688
虽然这些可以用来增强沙箱,甚至可以用作沙箱的一个组成部分,但其中一些会降低安全性。用户命名空间在非特权(默认)时,会从内核中暴露出更大的攻击面。启用用户命名空间后,非特权进程可以利用许多 内核 漏洞。 在某些内核上,您可以通过设置为 0 来禁用非特权用户命名空间,或者,如果该特定 sysctl 在您的系统上不可用,则设置为 0。如果您正在构建自己的内核,则可以设置为全局禁用用户命名空间。 kernel.unprivileged_userns_clone
user.max_user_namespaces
CONFIG_USER_NS=n
强制访问控制
MAC 是一个框架,用于在白名单的基础上定义程序可以做什么和不能做什么。一个程序被表示为一个主题。程序想要操作的任何东西,例如文件、路径、网络接口或端口,都表示为一个对象。访问对象的规则称为权限或标志。采用实用程序的AppArmor策略ping
,并添加注释:
#include <tunables/global>
/bin/ping {
# use header files containing more rules
#include <abstractions/base>
#include <abstractions/consoles>
#include <abstractions/nameservice>
capability net_raw, # allow having CAP_NET_RAW
capability setuid, # allow being setuid
network inet raw, # allow creating raw sockets
/bin/ping mixr, # allow mmaping, executing, and reading
/etc/modules.conf r, # allow reading
}
实施此策略后,该ping
实用程序如果受到破坏,将无法从您的主目录读取、执行 shell、写入新文件等。这种沙盒用于保护服务器或工作站。除了 AppArmor,一些流行的 MAC 包括SELinux、TOMOYO和SMACK。这些通常在内核中作为Linux 安全模块或 LSM 实现。这是 Linux 下的一个子系统,它为模块提供各种操作(如更改凭据和访问对象)的钩子,以便它们可以强制执行安全策略。
管理程序
管理程序是虚拟化软件。它通常利用允许隔离所有系统资源的硬件功能,例如 CPU 内核、内存、硬件等。虚拟化系统不仅相信它有 root,而且相信它有 ring 0(内核模式)。硬件要么由 CPU 抽象(CPU 内核和内存本身的情况),要么由管理程序软件模拟(对于更复杂的硬件,例如 NIC)。因为客户相信它拥有整个系统,所以可以在该架构上运行的任何东西都将倾向于在虚拟机中运行,允许带有 Windows 客户机的 Linux 主机或带有 Solaris 客户机的 FreeBSD 主机。理论上,管理程序会阻止来宾中的任何操作影响主机。
有助于了解如何设置来宾的低级有用资源是KVM API上的 LWN 文章。KVM 是 Linux 和Illumos(Solaris 的开源分支系列)支持的内核接口,用于设置虚拟机。由于它的级别相当低(仅通过对字符设备打开的文件描述符上的 IOCTL 进行交互/dev/kvm
),它通常是QEMU等项目的后端。KVM 本身利用特权硬件虚拟化功能,例如Intel 处理器上的VT-x和 AMD 上的 AMD-V。
管理程序(例如流行的Cuckoo 沙箱)通常用于协助进行恶意软件分析。它创建一个可以运行恶意软件的虚拟机,并分析系统的内部状态、读取内存内容、转储内存等。由于它运行完整的操作系统,恶意软件通常更难以意识到它是运行虚拟化。尽管一些恶意软件可以尝试检测虚拟化(具有不同程度的复杂性),但常见技术(例如将虚拟调试器附加到自身以使其无法调试)可能会被愚弄。管理程序检测本身是一个非常广泛和复杂的主题。
管理程序通常(ab)用于安全性,例如Qubes OS或Bromium(使用Xen 管理程序分别隔离 Fedora 和 Windows)。由于 Xen 中的错误反复出现,这是否是一个好主意经常引起争论。来自 OpenBSD 的创始人 Theo de Raddt 的一句著名且颇为粗俗的引述,关于依赖于安全性的虚拟化主题:
如果您认为全球范围内无法编写没有安全漏洞的操作系统或应用程序的软件工程师,那么您绝对是被骗了,然后可以转身突然编写没有安全漏洞的虚拟化层。
虚拟机管理程序是否是安全性的好选择取决于许多因素。它通常更易于使用和维护,因为它隔离了整个客户操作系统,但它具有很大的攻击面并且不提供细粒度的保护。
容器
容器类似于虚拟机管理程序,但它们不使用虚拟化,而是使用命名空间。每个容器都将每个资源放在自己的命名空间中,从而允许每个容器运行独立的操作系统。容器上的 init 进程将自己视为以 root 身份运行的 PID 1,但主机将其视为另一个非 init 和非 root PID。但是,由于它们共享主机的内核,它们只能运行与主机相同类型的操作系统。此外,虽然容器可以拥有可以执行特权操作的根进程,例如设置网络接口(当然,只能在该容器的命名空间中),但它们不能更改会影响所有容器的全局内核设置。Docker和OpenVZ是流行的容器实现。
因为容器从根本上依赖于各种实现的用户命名空间(Docker 的标准 Linux 命名空间,以及 OpenVZ 的定制命名空间技术),所以它们经常被批评为提供较差的安全性. 容器逃逸和权限提升漏洞在这些系统上并不少见。这些安全问题的原因源于命名空间 root 用户可以以新的和意想不到的方式与内核交互。虽然内核被设计为不允许命名空间 root 用户对系统进行任何明显危险的、不能保存在命名空间中的更改(如 sysctl 调整),但与非特权用户相比,root 仍然能够与更多的内核交互过程。正因为如此,一个只能由 root 在设置虚拟网络接口的过程中利用的漏洞,例如,如果该进程能够进入用户命名空间,则该进程可能会被该进程利用。即使系统调用只能“看到”容器的网络接口,这也是一个问题。
最后,用户只需命名空间允许未经授权的用户交互与远内核的多。表面积增加到这样的程度,以至于许多原本相对无害的漏洞反而变成了 LPE。当内核开发人员在编写只有 root 可以交互的代码时倾向于不保持安全心态时,就会发生这种情况。
其他技术
Linux 肯定不是唯一具有沙盒功能的操作系统。许多其他操作系统都有自己的技术,以各种不同的方式和不同的威胁模型实现:
Windows 上的AppContainer提供类似于 chroot 和命名空间组合的隔离。域、文件、网络,甚至窗口都是隔离的。
FreeBSD 上的监狱建立在 chroot 的概念之上。它为在监狱中运行的程序分配一个 IP 地址,并为其提供自己的主机名。与 chroot 不同,它是为安全而设计的。
Solaris上的区域是在主机内核下运行 Solaris 用户空间副本的高级容器。它们类似于 FreeBSD Jails,但功能更丰富。
快速浏览一下关于沙盒的维基百科页面:
在计算机安全中,沙箱是一种用于分离正在运行的程序的安全机制,通常是为了减少系统故障或软件漏洞的传播。它通常用于执行未经测试或不受信任的程序或代码,可能来自未经验证或不受信任的第三方、供应商、用户或网站,而不会对主机或操作系统造成损害。沙盒通常为来宾程序运行提供一组严格控制的资源,例如磁盘和内存上的暂存空间。网络访问、检查主机系统或从输入设备读取的能力通常被禁止或受到严格限制。
在提供高度受控的环境的意义上,沙盒可以被视为虚拟化的一个具体示例。沙盒经常用于测试可能包含病毒或其他恶意代码的未经验证的程序,而不允许软件损害主机设备。
因此,沙盒是一种实现,它创建一个受控和受限的环境来执行/分析不可信的应用程序/任务/任何可以在计算机上运行的东西。
它们大多是用虚拟机实现的,因为它们就像一个实际的物理系统,可以轻松设置和监控。
另一种方法是安全计算模式(seccomp),在同一个维基百科页面中提到:
它是一个内置在 Linux 内核中的沙箱。激活后,seccomp 仅允许
write()
、read()
、exit()
和sigreturn()
系统调用。
从 Wikipedia 页面引用操作系统级虚拟化
然后是众所周知的操作系统级虚拟化,也称为容器化。它是一种操作系统功能,其中内核允许存在多个隔离的用户空间实例。此类实例称为容器、分区、虚拟化引擎 (VE) 或监狱(FreeBSD 监狱或 chroot 监狱)。从运行在其中的程序的角度来看,它们可能看起来像真正的计算机。在普通人的计算机操作系统上运行的计算机程序可以看到该计算机的所有资源(连接的设备、文件和文件夹、网络共享、CPU 能力、可量化的硬件能力)。但是,在容器内运行的程序只能看到容器的内容和分配给容器的设备。
还有其他一些使用沙盒的方法和应用程序,例如 Java 运行时环境。沙盒和其他虚拟化技术被大量用于在最终发布之前测试应用程序和环境,用更少的硬件同时执行许多任务,分析恶意软件等。我希望你对用途和可用性的扩展有一个简要的了解。随意在下面询问您想要的任何其他内容!
沙盒是一种安全机制,用于在与常规环境分离的隔离环境中执行程序,而不影响系统的正常运行。它有助于执行恶意软件分析、测试新代码和执行不熟悉的代码。
简而言之,沙盒是在进入网络之前测试来自不受信任来源的任何传入文件,这是通过在虚拟机上执行或打开文件并检查它是否显示任何潜在的安全威胁来完成的