介绍
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 | scmp_filter_ctx ctx; //声明上下文,将创建seccomp过滤容器 |
为了调用,在编译执行文件时需要加一个参数 -lseccomp
像这样 gcc -o a a.c -lseccomp
,将它链接在seccomp库
下载seccomp sudo apt libseccomp-dev
一个简单的seccomp代码,它的作用是当进程调用 execve
系统调用时,内核将会立即终止该进程,execl("/bin/cat", "cat", "/flag", (char *)0);
就是有关execve的调用
1 |
|
正常输出
1 | └─$ ./seccomp |
加了沙盒代码后的输出,Bad system call
1 | └─$ ./seccomp |
具体可以starce ./seccomp查看
1 | └─$ strace ./seccomp |
演示子进程继承seccomp
更换代码,将exe改为read
1 | seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0); |
追踪strace cat catflag,可以发现cat调用了read,从文件中读入了flag,cat是seccomp.c的子程序,所以在这里使用cat来演示继承
1 | mmap(NULL, 139264, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc515eae000 |
运行结果
1 | └─$ ./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 位。这些架构具有不同的系统调用编号,分别取决于 rax
和 eax
。默认情况下,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