Dify 沙箱逃逸漏洞 RCE 分析

前言



社区上还没有人分析过Dify的沙箱逃逸,n8n的分析了很多,今天来分析分析dify的,而且阿里云漏洞库给到了9.8的评分



image.png



漏洞描述

Dify是一款开源的LLM应用开发平台。在Dify的 代码节点 功能中,允许用户执行自定义的 JavaScript 代码。该漏洞是由于 Dify 沙箱(dify-sandbox)在执行用户提供的 JavaScript 代码时,未能在安全限制生效之前对用户代码进行充分的隔离。攻击者可以通过在 JavaScript 代码中重写全局函数(例如 parseInt),在沙箱安全限制(如 seccomp)生效之前执行任意系统命令,从而实现沙箱逃逸并获得宿主机 root 权限,进而获取敏感信息(如 API 密钥、内部服务器地址、K8s token)。

环境搭建

沙盒版本低于 0.2.11 就好了

在 docker 文件里面修改一下,这个不需要修改整个 dify

复现起来比较方便

只重启 sandbox

漏洞复现

POC

写一个代码执行的工作流



节点加入逃逸代码







成功

漏洞分析

根据 Huntr 披露的细节

漏洞的根源在于 Go 语言编写的 Runner (nodejs.go) 如何处理用户代码。它没有使用安全的沙箱上下文(如 vm.runInContext),而是采用了简单的字符串拼接

prescript.js



这样的顺序有什么问题呢?梳理清楚执行顺序

问题 1:parseInt 是 JavaScript 全局函数,可以被用户代码覆盖
问题 2:parseInt 在 difySeccomp 之前被调用
问题 3:用户代码拼接在 prescript.js 之后,会在 parseInt 调用前执行

nodejs.go

可以看到完全没有隔离,我记得当时有一个 jsp 的 webshell 就是利用这个原理免杀的

最终生成的 JS 脚本

当 Go 程序完成拼接后,Node.js 进程实际接收到的 final_script 长这样



在 JavaScript 中,函数声明具有最高的提升优先级,也就是在代码执行任何一行之前,JS 引擎会先扫描整个作用域,将所有 function xxx() {} 提升到内存顶部

所以在编译阶段,Node.js 解析整个拼接后的脚本,发现了用户代码中自定义的函数,将全局作用域中的 parseInt 引用指向用户的恶意函数,覆盖了原生的 parseInt

导致了漏洞

Payload 解析

JavaScript 函数声明提升机制

在 JavaScript 中,**函数声明(Function Declaration)**具有最高的提升优先级:

JavaScript 引擎在执行代码前,会先扫描整个作用域,将所有 function xxx() {} 声明提升到内存顶部

执行流程



总结

其实总的下来,就是一个这样的顺序,而这个漏洞最重要的就是执行顺序



漏洞修复

https://github.com/langgenius/dify-sandbox/commit/a0c74d335e380d0a51595cdc0ad975a064a6127b



漏洞的本质就是执行的顺序,安全边界 Seccomp 限制优先执行就好了

调整代码执行顺序:确保 difySeccomp(安全限制)在用户代码执行前调用,彻底避免沙箱生效前的 “窗口期”。例如,修改 prescript.js 与用户代码的拼接逻辑,让 difySeccomp 优先执行:



参考

https://athink.cn/securitynews/detail/?id=687dd9dd10b54e7878ef6d1b

https://huntr.com/bounties/f8dc17a3-5536-4944-a680-24070903cd2d

标签: NODE.JS, JavaScript, 安全漏洞, 代码执行, RCE, Dify, 沙箱逃逸

添加新评论