Langroid 不完整的 eval() 沙箱让一个提示词就能执行主机代码
Langroid 早先针对 TableChatAgent 代码注入缺陷的补丁留下了一个可选路径:其中的 eval() 沙箱忘记剥离 Python 内置函数,重新打开了未经身份验证的远程代码执行。
这是什么?
2026 年 7 月 6 日,Langroid 收到了一份严重级别的安全公告。Langroid 是一个广受欢迎的开源 Python 框架,用于构建基于 LLM 智能体的应用。它的 TableChatAgent 允许模型通过编写 pandas 表达式来回答关于表格数据的问题,随后由该智能体执行这个表达式。这一缺陷记录于 GitLab 与 GitHub 的安全公告库,能够让一个可以影响模型输出的攻击者在主机上获得未经身份验证的远程代码执行。它被评为 CVSS 10.0 —— 满分 —— 因为其影响突破了该组件自身的安全边界。
此案例的教训在于它的历史。同一个智能体此前就有过一处代码注入问题,已于 2025 年 5 月修复,该修复”默认对输入进行清洗”。而这次的新发现属于不完整缓解:一个可选的求值模式用一个只是看起来安全的沙箱,重新打开了这个缺口。
工作原理
TableChatAgent 使用 pandas 的 eval() 来执行模型生成的表达式。当启用更宽松的求值模式时,代码试图通过给 Python 内置的 eval() 传入一个空的局部命名空间来隔离执行 —— 其直觉是”没有变量,就没有危险”。问题在于:清空局部变量(locals)丝毫不影响全局变量(globals)。如果 globals 字典没有显式移除 __builtins__,Python 解释器会在求值时悄悄地重新注入整个内置函数命名空间。
仅此一处遗漏,就把一切要紧的东西都还给了表达式 —— 包括导入机制 —— 于是模型撰写的文本便能触及 os.system,以进程的身份运行 shell 命令。由于该智能体把 LLM 的输出直接送入这个求值器,任何能引导模型的输入(一份被投毒的文档、共享数据集中一行恶意数据、面向公众的机器人的某位用户)都成为通向代码执行的路径。
# CWE-94 反模式:一个"空的"、其实并不隔离的沙箱
eval(expr, {}, {}) # globals={} -> Python 会重新注入 __builtins__
eval(expr, {"__builtins__": {}}, {}) # 只有这样才真正移除了内置函数
这里没有任何可用于攻击的载荷。教训是结构性的:清空局部变量并不是沙箱。移除 __builtins__ 是最低要求,而即便如此也算不上牢固的边界 —— Python 存在有据可查的逃逸手法,可通过对象属性链重新回到 __builtins__。请把”我们运行的是一个受限的 eval()”视为一个危险信号,而非一项防护措施。
为什么重要
这是智能体框架反复出现的失效模式:模型是文本生成器,但框架把这段文本连接到了一个动作 —— 这里就是任意 Python 代码。本应存在于”模型建议了这个”与”主机执行了这个”之间的信任边界缺失了,于是影响模型的输出就等同于影响 shell。
不完整修复这一角度同样重要。第一个补丁把安全行为设为默认,却留下了一个通过开关即可访问的更强模式。为了获得能力而打开该开关的团队,因此继承了一个满分严重级别的 RCE,而调用处并没有新增”unsafe(不安全)“警告。可选的强大能力恰恰是回归缺陷的藏身之处,因为启用它的人很少会重新审计这个开关所关闭的安全防护。
防御
升级。 该问题已在 Langroid 0.65.2 中修复;请离开任何更早的版本,并在锁文件中固定版本。
绝不对模型生成的代码使用 eval/exec。 变成代码的模型输出,按定义就是不可信输入。如果能不执行它,就不要执行 —— 优先采用受约束的、非代码的接口(结构化查询 DSL、白名单化的参数化操作集合),而不是”让模型写 Python、我们来执行”。
如果必须求值表达式,不要信任 eval()。 使用专门的受限求值器,例如遍历 AST 的解释器(如 asteval)或纯数值引擎(numexpr),它们绝不暴露导入或属性访问。把 {} 作为局部变量传入并不是一道边界;即使是 {"__builtins__": {}} 也只是一个减速带。
隔离整个智能体,而不仅仅是这次调用。 在一次性的容器或虚拟机中运行该进程,不带任何凭据、不带云令牌、工作区只读、默认拒绝出站网络。这样即便求值器被逃逸,落地之处也无物可窃、无处可回连。
审计你的”unsafe”开关。 在配置中搜索求值相关选项、full_eval、trust_remote_code 及其他 opt-in 项。每一项都应有一位负责人,能说清它关闭了哪项防护、以及为何接受这一风险。
扫描依赖。 CI 中的自动化安全公告检查本可以在其发布当天就标记出此问题。让公告扫描成为一道关卡,而不只是一块仪表盘。
状态
| 项目 | 详情 |
|---|---|
| 受影响 | Langroid TableChatAgent 与 VectorStore 的代码求值(full_eval 路径) |
| 根因 | eval() 使用空局部变量,但 globals 中的 __builtins__ 未被剥离(CWE-94) |
| 影响 | 主机上未经身份验证的远程代码执行;CVSS 10.0(安全边界改变) |
| 受影响版本 | 0.65.2 之前的所有版本 |
| 修复版本 | 0.65.2 |
| 参考编号 | CVE-2026-54769 / GHSA-q9p7-wqxg-mrhc;此前不完整修复 CVE-2025-46724 / GHSA-jqq5-wc57-f8hj(0.53.15,2025 年 5 月) |
| 在野利用 | 披露时无公开 PoC,也无已知在野利用 |
本文总结的是已公开披露且已修复的研究,不包含任何可操作的攻击指令。
Sources
- → https://advisories.gitlab.com/pypi/langroid/CVE-2026-54769/
- → https://github.com/langroid/langroid/security/advisories/GHSA-q9p7-wqxg-mrhc
- → https://github.com/langroid/langroid/security/advisories/GHSA-jqq5-wc57-f8hj
- → https://www.thehackerwire.com/cve-2026-54769-langroid-is-a-framework-for-building-large-language-model/