Linux沙箱之seccomp介绍
arch3rn4r

介绍

seccomp (Secure Computing Mode) 是 Linux 内核的一种安全机制,用于限制进程可以调用的系统调用集。本质上是linux系统调用的防火墙,可查看文档http://man7.org/linux/man-pages/man3/seccomp_rule_add.3.html

seccomp很强大,它可以遗传给子进程,甚至能限制root用户运行的进程,而且可以自己编写复杂的规则来达到目的,能够管理系统调用,禁止某些系统调用,允许某些系统调用,甚至是基于它们的参数来过滤系统调用。

seccomp原理

seccomp 的核心工作原理是基于 eBPF(扩展的 Berkeley Packet Filter),一种内核中的 “虚拟机” 机制,来定义和执行系统调用的过滤规则。**BCC (BPF Compiler Collection)** 就是一个基于 eBPF 的工具集,可以对系统进行深度的追踪分析,帮助开发者理解和优化系统行为。

BPF 最初是一个简单的虚拟机,允许用户在内核中安装过滤器程序,这些程序可以定义哪些网络数据包被捕获、过滤或丢弃。而eBPF是利用库,比如seccomp等建立起过滤器,是 BPF 的扩展,最初用于扩展网络过滤功能,但现在它被广泛用于监控、追踪、和执行更多类型的内核任务,不仅限于网络过滤。eBPF 是一个通用的、可编程的内核沙箱系统,可以在不修改内核源码的情况下,通过动态加载字节码,增强内核功能。

工具

用于提取 seccomp 规则

https://github.com/david942j/seccomp-tools

演示环节

创建沙盒的主要代码:

1
2
3
4
scmp_filter_ctx ctx;   //声明上下文,将创建seccomp过滤容器
ctx = seccomp_init(SCMP_ACT_ALLON); //初始化容器
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0);//添加规则
seccomp_load(ctx);//调用这个沙盒

为了调用,在编译执行文件时需要加一个参数 -lseccomp 像这样 gcc -o a a.c -lseccomp,将它链接在seccomp库

下载seccomp sudo apt libseccomp-dev

一个简单的seccomp代码,它的作用是当进程调用 execve 系统调用时,内核将会立即终止该进程,execl("/bin/cat", "cat", "/flag", (char *)0);就是有关execve的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#define _GNU_SOURCE 1
#include <sys/sendfile.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <seccomp.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
int main(int argc, char **argv)
{
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_ALLOW);
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0);
seccomp_load(ctx);

execl("/bin/cat", "cat", "/catflag", (char *)0);
}

正常输出

1
2
3
└─$ ./seccomp
#!/bin/sh
echo yougettheflag!

加了沙盒代码后的输出,Bad system call

1
2
└─$ ./seccomp                                                                  
错误的系统调用

具体可以starce ./seccomp查看

1
2
3
4
5
6
7
└─$ strace ./seccomp   
...
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) = 0
seccomp(SECCOMP_SET_MODE_FILTER, 0, {len=8, filter=0x5654398a6f60}) = 0
execve("/bin/cat", ["cat", "catflag"], 0x7fff389874c8 /* 57 vars */) = 59
+++ killed by SIGSYS +++
错误的系统调用

演示子进程继承seccomp

更换代码,将exe改为read

1
2
3
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0);
修改为
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(read), 0);

追踪strace cat catflag,可以发现cat调用了read,从文件中读入了flag,cat是seccomp.c的子程序,所以在这里使用cat来演示继承

1
2
3
4
5
6
7
mmap(NULL, 139264, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc515eae000
read(3, "#!/bin/sh\necho yougettheflag!\n\n", 131072) = 31
write(1, "#!/bin/sh\necho yougettheflag!\n\n", 31#!/bin/sh
echo yougettheflag!

) = 31
read(3, "", 131072)

运行结果

1
2
└─$ ./seccomp                                                                                  
错误的系统调用

strace ./seccomp 可以发现read进程被kill了

1
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=123035, ...}, AT_EMPTY_PATH) = 0mmap(NULL, 123035, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fc5b7dc7000close(3)                                = 0openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3read(3, "", 832)                        = 0+++ killed by SIGSYS +++错误的系统调用

逃逸

宽松政策

很多程序使用seccomp,比如docker,firefox等,它们使用seccomp来进行保护。同时,这类应用需要和用户进行交互,需要使用互联网,需要高性能,这通常意味着直接调用内核来实现功能,使用系统调用而不是通过它们的父进程。

系统调用一直在更新,也就意味着沙盒很难一直跟进系统新功能。有些docker漏洞就是因为不正确的seccomp配置导致的。

其中一个例子是在沙盒中允许使用ptrace,ptrace是linux的调试功能,允许它作为调试器附加到另一个进程,监视它的执行,改变内存,改变寄存器,可以获得完全控制权。最终能通过另一个进程逃离沙盒。可看这篇文章 (KCTF2024 - 第八题星门 题解 与 ptrace绕过seccomp讨论https://bbs.kanxue.com/thread-283186.htm)

其他系统调用

  • sendmsg()

不仅可以发送从文件中读取的数据,还可以发送打开文件描述符本身。当在shellcode中执行open,他返回flag的文件描述符,比如数字3.当这个数字被发送到另一个进程,它只是数字3,但是使用sendmsg可以发送包装好的元数据,表明这是一个文件,然后内核看到这个元数据,并将这个打开得内核跟踪的文件传输到你要发信息的进程中。

  • prctl()

抓包

  • process_vm_writev()

该系统调用可以将内存直接写入另一个程序

混淆

源于:AMD,AMD64向后兼容x86 ,linux支持32位和64位代码在同一个进程中交替进行(沙箱名字:native client https://developer.chrome.com/docs/native-client?hl=zh-cn)

架构混淆

linux对于x86和amd64有不同的系统调用定义,在arm64上使用系统调用指令有两种不同的方式来触发它们,

比如exit()在amd64中系统调用参数是60,在x86和x86_32中是1

有两种类型的系统调用: syscall 用于 64 位和 int 0x80 用于 32 位。这些架构具有不同的系统调用编号,分别取决于 raxeax 。默认情况下,seccomp 将终止所有 32 位系统调用。

也就是说如果在之前的seccomp.c代码里加上32位标准的read调用,就有可能绕过沙箱。在默认配置(前面出现的配置)中,这样的错误不会发现,但是在一些非默认配置中就可能出现这样的混淆错误,从而能被利用

侧信道

如果只是想读取信息而不是拿到shell。

sleep() 根据进程运行时间来传递数据,类似于sql盲注

一个bit一个bit的传输

崩溃与无崩溃:在某些情况下,很明显程序崩溃,因为应用程序明确地通过错误消息告诉您,或者只是缺少一些预期的输出。这可以通过崩溃或继续执行来告诉你 1 位信息,利用这一点的方法类似于使用程序的运行时。

内核漏洞

https://github.com/allpaca/chrome-sbx-db

参考资料和推荐资源

http://man7.org/linux/man-pages/man3/seccomp_rule_add.3.html

https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/

https://bbs.kanxue.com/thread-283186.htm

https://github.com/allpaca/chrome-sbx-db

https://book.jorianwoltjer.com/binary-exploitation/sandboxes-chroot-seccomp-and-namespaces

eBPF****相关扩展

https://github.com/iovisor/bcc

https://xz.aliyun.com/t/11480?time__1311=Cq0xRDnD0D2A0QD%2FWr90Djxmw4VKYzea4D

https://ebpf.io/what-is-ebpf/

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
总字数 39.3k 访客数