langrq的blog

LangGraph Checkpointer 漏洞链:从 SQL 注入到远程代码执行

文章要点

解读 Check Point Research 对 LangGraph 持久化层的分析——三个 CVE 如何串联成 RCE,谁该升级,以及自托管 Agent 应用该吸取的教训。

Check Point Research 在 2026 年 6 月发了一篇安全分析:From SQLi to RCE – Exploiting LangGraph's Checkpointer。研究对象是 LangGraph——LangChain 生态里用来构建「有状态、多步 Agent」的开源框架,PyPI 月下载量超过 5000 万

文章核心结论并不复杂:Agent 需要记忆,记忆落在 Checkpointer 里;Checkpointer 若把用户输入拼进 SQL、再用不安全的方式反序列化 checkpoint,整条链可以打到 RCE。

下面是我读完原文后的重排解读,不是翻译稿。

一分钟版

项目内容
影响组件LangGraph 的 SQLite / Redis Checkpointer(持久化层)
关键 CVECVE-2025-67644(SQLite SQL 注入)、CVE-2026-28277(msgpack 不安全反序列化)、CVE-2026-27022(Redis 同类注入)
攻击链用户可控 filter → SQL 注入写入假 checkpoint → 反序列化恶意 msgpack → 执行任意命令
谁危险自托管 LangGraph、使用 SQLite/Redis Checkpointer,且把 get_state_history(filter=...) 暴露给不可信输入的应用
谁相对安全LangChain 托管的 LangSmith Deployment(原 LangGraph Platform),跑 PostgreSQL,不在此次影响面内
怎么修升级到 langgraph-checkpoint-sqlite 3.0.1+langgraph 1.0.10+langgraph-checkpoint-redis 1.0.2+langgraph-checkpoint 4.0.1+

Checkpointer 是什么

LangGraph 把 Agent 的执行过程切成多步,每一步的状态(对话、工具结果、中间决策)需要存下来,以便:

  • 断点续跑
  • 回溯历史
  • 多 Agent 协作时共享上下文

Checkpointer 就是这套「记忆层」。常见实现包括 SQLite、PostgreSQL、Redis。SQLite 里有一张 checkpoints 表,其中 metadata 存 JSON 上下文(如 user_idstep),checkpoint 存序列化后的状态 blob。

问题出在:读这些记忆时,框架假设调用方是可信的——但生产里 filter 往往来自 API 参数。

漏洞一:metadata 过滤里的 SQL 注入(CVE-2025-67644)

触发路径

应用若调用 get_state_history(filter=用户输入),内部会走到 Checkpointer 的 list(),用 filter 字典按 metadata 字段查历史 checkpoint。

框架把 filterkey 拼进 SQL 的 JSON 路径:

python
f"json_extract(CAST(metadata AS TEXT), '$.{query_key}') {operator}"

query_key 若含单引号,可逃逸 JSON 路径字符串,注入任意 SQL——典型 SQL 注入

为什么 SQL 注入还不够「致命」

注入本身只能改查询;真正危险的是:list() 查出来的每一行都会把 checkpoint 列交给反序列化逻辑:

python
self.serde.loads_typed((type, checkpoint))

也就是说:谁能控制查询结果里的 (type, checkpoint) 元组,谁就控制了反序列化输入。

攻击者用 UNION SELECT 塞一行假数据:type='msgpack'checkpoint 为精心构造的二进制 payload。查询照常返回,框架照常反序列化——SQL 注入成了「投毒入口」。

漏洞二:msgpack 扩展类型导致 RCE(CVE-2026-28277)

LangGraph 用 msgpack 存 checkpoint。反序列化时注册了自定义 ext_hook,其中 EXT_CONSTRUCTOR_SINGLE_ARG 的处理逻辑等价于:

python
# 简化理解:从 payload 解出 (module, name, arg) 三元组
return getattr(importlib.import_module(tup[0]), tup[1])(tup[2])

攻击者构造 msgpack,让三元组变成 ("os", "system", "恶意命令"),执行路径就是:

  1. import os
  2. os.system
  3. os.system("攻击者命令")

任意代码执行。 Pickle 默认关闭;JSON 路径在 Check Point 同期研究(LangGrinch)里未直接导致 RCE;msgpack + 自定义 ext_hook 是这条链的终点。

漏洞三:Redis Checkpointer 同类问题(CVE-2026-27022)

Redis 实现里,filter 的 key 同样被直接插值进查询而非参数绑定,注入类与 SQLite 版同构。前提一致:应用暴露带用户可控 filterget_state_history(),且使用 Redis Checkpointer。

完整攻击链(四步)

Text
用户请求带恶意 filter
        ↓
SQL 注入 → UNION 注入假 checkpoint 行(type=msgpack, blob=恶意)
        ↓
list() 遍历结果 → loads_typed() 反序列化
        ↓
ext_hook 执行 os.system(…) → RCE

入口通常是 get_state_history()filter 参数未校验。这在「给用户提供按 metadata 筛历史会话」的产品功能里并不罕见——例如按 user_idsession_id 过滤——若 key 或结构来自前端/URL,风险就成立了。

谁该紧张,谁可以松口气

需要排查:

  • 自托管 LangGraph,Checkpointer 用 SQLite 或 Redis
  • 代码或 API 把 filter 字典(尤其 key) 直接传给 get_state_history() / checkpointer.list()
  • 服务对 不可信用户 开放(多租户 SaaS、公网 Agent 面板等)

相对不在影响面:

  • 只用 LangChain LangSmith Deployment 托管、后端 PostgreSQL 的团队(官方说明该路径不受此次 SQLite/Redis 问题影响)

额外发现: 研究还指出 SQLite/PostgreSQL Checkpointer 里 LIMITttl 等整型参数存在拼接式 SQL(Python 类型提示运行时不强制),LangChain 团队在披露过程中一并修了——属于纵深防御,说明持久化层曾有多处「字符串拼 SQL」的习惯。

升级清单

建议版本
langgraph-checkpoint-sqlite3.0.1+(CVE-2025-67644)
langgraph-checkpoint-redis1.0.2+(CVE-2026-27022)
langgraph-checkpoint4.0.1+(CVE-2026-28277)
langgraph1.0.10+

披露时间线:2025-11 报给 LangChain;SQLite 修复 2025-12 公开;Redis 2026-02;msgpack 2026-03。社区在 2025 年 11–12 月也有独立研究触及相同 CVE,细节见 LangChain 安全公告。

对写 Agent 应用的人意味着什么

  1. 持久化层是信任边界。 Agent「记忆」和 Web 应用「数据库」一样,不能默认只有开发者会调用。
  2. 不要把用户输入拼进 SQL 路径。 filter 的 key 必须是白名单字段,value 必须参数化——不能学框架早期 _metadata_predicate 的做法。
  3. 反序列化格式要有态度。 自定义 msgpack ext 若等价于「动态 import + 调用任意函数」,等于内置后门;生产应用应限制可反序列化类型,或不用可执行语义的扩展。
  4. 自托管 ≠ 免运维。 框架下载量大,不等于默认配置适合公网;Checkpointer、工具执行、文件读写要一起纳入威胁模型。

若你在本博客 AI Agent 系列 里跟过 LangChain 本地 Agent 教程,这次漏洞正好提醒:能跑起来的 Demo 和能上线的服务之间,差的就是这类持久化与输入边界。

参考


本文为技术解读,不构成安全审计结论。生产环境请对照官方 advisory 与依赖树自行验证版本。

评论

加载评论中...