SECCOMP_RET_TRACE沙箱绕过
1673799454684900 二进制安全 115浏览 · 2025-03-14 04:37

CVE-2022-30594是一个沙箱的漏洞,最近复现题目刚好接触到了相关知识,选择记录下来,便于后续学习,例题使用的是羊城杯2024年的hard sandbox

题目情况

简单看一下题目情况,

Plain Text
复制代码








然后有一个沙箱



漏洞

基本就是一个纯板子题目,有一个uaf,重点其实在于沙箱。

沙箱把open和opanat都给ban了,当时做的时候基本想的就是openat2,但是圈圈说打不通,因为远程内核版本是5.4,5.6才有openat2

但是注意到这里并不是kill(事实上比赛的时候确实没看到,看到估计也不一定能写出来),这个trace和kill是完全不一样的,这个题目的重点也就是trace,我会尽量说的简单一些

分析

我们首先解释一下kill和track的区别,他们都是seccomp过滤器(也就是沙箱)的返回动作,但是触发完成之后的情况完全不同

当SECCOMP_RET_TRACE触发时,内核会通知ptrace跟踪器。这时候跟踪器可以干预系统调用的执行,比如修改参数或返回值。而SECCOMP_RET_KILL则是直接终止进程,不给任何机会。

而源代码里面给出的定义是这样的(seccomp(2) - Linux manual page (man7.org)

核心逻辑在于,当 seccomp 过滤器返回 SECCOMP_RET_TRACE 时,内核会在目标进程执行某个系统调用前,尝试通知一个通过 ptrace(2) 附加的调试器(tracer)。若此时没有调试器在监控该进程,系统调用会被直接阻断,返回错误状态 ENOSYS(表示“功能未实现”)。

调试器需通过 ptrace(PTRACE_SETOPTIONS) 设置 PTRACE_O_TRACESECCOMP 选项,才能接收到此类事件。当事件触发时,内核会向调试器发送 PTRACE_EVENT_SECCOMP 信号,并通过 PTRACE_GETEVENTMSG 传递 seccomp 过滤器返回值中的 SECCOMP_RET_DATA 字段数据。

调试器在收到通知后,可以干预系统调用的执行:

1 跳过系统调用:将系统调用号改为 -1,此时内核不会执行原系统调用,而是直接使用调试器设置的返回值作为结果。

2 篡改系统调用:修改系统调用号为其他有效值(如将危险的 execve 改为无害的 getpid),内核将执行修改后的调用。

3 伪造返回值:即使跳过系统调用,调试器也可通过寄存器注入任意返回值,欺骗被监控进程(tracee)。

Linux 4.8 之前的内核中,一旦调试器处理了 SECCOMP_RET_TRACE 事件,内核不会重新执行 seccomp 过滤器检查。这意味着:

沙箱逃逸风险:攻击者若能在被 seccomp 限制的进程中附加调试器(如通过 ptrace),可篡改系统调用绕过原有过滤规则。

防御要求:旧内核上的 seccomp 沙箱必须严格禁止 ptrace 调用,否则攻击者可利用调试器完全突破限制。

我知道这样一段基本上没人愿意看,确实有点难为人,举个例子:

假设你是一个员工(程序),每次要执行某个操作(系统调用)前,必须向保安(seccomp 沙箱)申请许可。保安有两种处理方式:

1 直接拒绝(KILL:保安说“不行!”并立刻赶走你。

2 找上级确认TRACE):保安说“我需要请示领导(调试器)”,此时:

如果没领导:直接拒绝你(返回错误 ENOSYS)。

如果有领导:打电话问领导怎么办。

领导可以让你换一个操作(比如你想打印文件,领导让你改成喝水)。

领导也可以假装你完成了操作(比如你想删文件,领导直接回复“删好了”,实际没删)。

旧系统漏洞(Linux <4.8)

保安(沙箱)只在第一次检查,之后完全听领导的。如果坏人冒充领导,就能让员工做任何事(比如换操作成“格式化硬盘”)。例子:你申请“拿一杯水”,坏人领导改成“拿保险柜密码”,保安不再检查,直接执行!

新系统修复(Linux ≥4.8)保安在领导修改操作后,会再次检查。如果领导让你做危险操作(比如“格式化硬盘”),保安依然会拒绝。

所以其实问题就来了,如果确定是4.8以上的内核,那么其实没办法改变

这题刚好就是5.4的,所以我们有别的操作

CVE-2022-30594

该漏洞存在于 Linux 内核 5.17.2 之前的版本 中,与 seccomp 安全机制的权限管理相关。攻击者可通过 PTRACE_SEIZE 操作绕过对 PT_SUSPEND_SECCOMP 标志的设置限制,导致 seccomp 的沙箱保护失效,从而执行本应被禁止的系统调用。

PTRACE_SEIZE(捕获进程控制权)的代码路径中,内核未正确校验调用者是否有权限设置 PT_SUSPEND_SECCOMP 标志。攻击者可利用此漏洞,绕过 seccomp 对敏感系统调用的限制,例如执行任意文件操作或提权。


想象你家的智能门锁(seccomp)本来可以防止小偷进入。

但这个门锁有一个后门:任何人用遥控器(ptrace)按下某个按钮(PT_SUSPEND_SECCOMP),就能让门锁失效。

exp编写

libc&&heap

最基本的libc和heap的泄露这里就不多赘述,原题是2.36的libc,但是因为方便,所以我调整成了ubuntu22.04自带的版本



largebin attack

这里我们还是largebin attack去攻击io list all,但是我们不选择调用system或者其他的,而是选择调用mprotect函数去开辟空间,这样才能执行我们的shellcode

这里我们攻击之后要把堆块修改回去,这样不会影响到我们后面的操作



这样我们就把stderr结构体伪造在我们的堆块上面了

伪造stderr结构体

这里用的是house of obstack,其他的链子应该都是可以的

这条链子的细节就不多赘述,重点其实是我们的shellcode如何编写

shellcode编写

羊城杯 2024 pwn writeup (9anux.org)

这里给出圈圈的博客作为讲解,引用他博客原文:

也就是说我们有办法对 seccomp 进行逃逸,其具体做法为:使用 fork 开一个子进程,子进程需要 ptrace(PTRACE_TRACEME, 0, 0,0); 来允许自己被父进程追踪,父进程需使用 ptrace(PTRACE_ATTACH, pid, 0, 0); 来追踪子进程。然后父进程在 wait() 阻塞等待子进程发起系统调用。一旦捕捉到,则子进程阻塞,父进程继续运行,此时需用 ptrace(PTRACE_0_SUSPEND_SEECOMP, pid, 0, PTRACE_0_TRACESECCOMP); 将被 TRACE 系统的调用改为允许运行,然后 ptrace(PTRACE_SCONT); 来恢复子进程的系统调用执行。由于我们不知道 flag 的路径和文件名是什么,所以直接使用 execve 来拿 shell

我们尝试一步一步编写shellcode

首先fork出一个进程

1. 父进程和子进程的创建

fork 系统调用

使用 fork 创建一个子进程。

父进程和子进程会同时运行,但通过fork的返回值区分彼此:

父进程的返回值是子进程的 PID。

子进程的返回值是 0。

2. 父进程追踪子进程

ptrace(PTRACE_ATTACH)

父进程通过 ptrace 附加到子进程,开始追踪子进程的行为。

PTRACE_ATTACHptrace 的一个选项,用于附加到目标进程。

wait4 系统调用

父进程通过 wait4 等待子进程的状态变化(例如子进程发起系统调用)。

当子进程被 ptrace 追踪时,子进程的系统调用会被暂停,父进程可以捕获并修改子进程的行为。

3. 绕过 seccomp 限制

ptrace(PTRACE_SETOPTIONS)

父进程通过 ptrace 设置选项,允许追踪子进程的 seccomp 事件。

PTRACE_O_TRACESECCOMP 是一个选项,用于追踪子进程的 seccomp 事件。

ptrace(PTRACE_CONT)

父进程通过 ptrace 恢复子进程的执行。

子进程的系统调用会被允许执行,从而绕过 seccomp 的限制。

子进程执行 /bin/sh

execve 系统调用

子进程通过 execve 执行 /bin/sh,获取一个 shell。

/bin/sh 的路径被拆分为两部分(/bin/bash\x00),并通过 push 指令拼接到栈上。

5. 退出逻辑

exit 系统调用

如果 fork 失败,程序会直接退出。

效果

最后补全exp





0 条评论
某人
表情
可输入 255