ToLO 分类体系
ToLO 的 taxonomy 有一个容易误解的地方:它不是按”模型攻击手法”分类,而是按”同一个 LLM-output source 进入了哪类危险 sink”分类。因此七子类共享同一个根因,只是在后果和修复方式上不同。
这一章把 ToLO 分类讲清楚:七子类怎么分、五类防御怎么对应、为什么”分子类”对教学和检测都有用。
这一章给你什么
| 你将能做到 | 用到的内容 |
|---|---|
把任意 ToLO 案例归入 ToLO-{Exec, Shell, SQL, Path, SSRF, Deser, Template} 七子类之一 | Core Patterns |
| 给每个子类挑出类型匹配的修复方案 | Defensive Patterns |
解释为什么”用了 JSON 解析”不算修复 ToLO-SQL | Defensive Patterns §C_SAFE^safe-codec |
| 区分 “降低风险” 与 “切断 ToLO” | 本页 §“通道防御 vs sink 前防御” |
如果你已经知道 7 个子类的名字和 5 个 sanitizer 类的对应,可以跳到 Threat Model。
你需要先知道什么
读这一章前,你需要熟:
S_LLM是什么(LLM 输出 source 五子集)— 见 先修知识 §5 与 术语表。- source / sink / sanitizer 的基本含义 — 见 先修知识 §5。
- 至少能写出 3 个具体 sink 函数(如
eval、cursor.execute、open)。
如果上面任一不熟,先回去补。
一张图看完全章

整章在解释:为什么 S_LLM 是同一个 source、为什么 sink 要分七类、为什么 C_SAFE 必须类型匹配、错配的”防御”为什么不算 sanitizer。
分类原则:source 锚定,sink 开放
这一章的所有名字背后都有同一组关系:
同一个 source class: S_LLM (LLM 输出五子集) │ ┌─────────┬───────┼────────┬─────────┬────────┐ ▼ ▼ ▼ ▼ ▼ ▼ ToLO-Deser ToLO-Exec ToLO-Shell ToLO-SQL ToLO-Path ToLO-SSRF ToLO-Template (CWE-502) (CWE-94) (CWE-78) (CWE-89) (CWE-22) (CWE-918) (CWE-1336) │ │ │ │ │ │ └─────────┴───────┼────────┴─────────┴────────┘ ▼ 不同的 sink: 反序列化 / 代码执行 / shell / SQL / 路径 / URL / 模板 │ ▼ 对应不同的 C_SAFE 类型匹配 sanitizerS_LLM 是锚点。无论攻击者通过 direct prompt injection、RAG 投毒、工具响应控制还是模型供应链污染影响输出,只要框架把该输出当成可信内部数据传给危险 sink,就进入 ToLO 分析。
S_DANGER 是开放集合。本站当前使用七个教育性子类:ToLO-{Deser, Exec, Shell, SQL, Path, SSRF, Template}。这些名字帮助读者把 ToLO 映射到熟悉的 CWE family,但不声明 taxonomy 已经穷尽。未来发现新 sink(比如 LLM 输出影响系统资源限制 / IPC / RPC 等),允许扩展。
C_SAFE 是判断修复是否成立的关键。验证、白名单、参数化、安全编解码和能力门控必须与 sink 类型匹配。错配的 sanitizer 不应被当成真正防御。
为什么要分子类
分子类有四个用处。
1. 帮助快速理解后果
ToLO-Shell一看就知道后果与命令执行相关。ToLO-SSRF一看就知道后果与网络请求相关。ToLO-Path一看就知道后果与文件读写相关。
这种”名字传递严重程度”的特性,对工程沟通有用。安全人员看到 ToLO-Exec 应该比 ToLO-Template 优先处理。
2. 帮助选择类型匹配的防御
不同 sink 需要不同 sanitizer。错配的 sanitizer 等于没有 sanitizer:
| Sink | 类型匹配的 sanitizer | 错配的”假 sanitizer” |
|---|---|---|
| SQL | 参数化查询 (%s placeholder) | URL 编码、HTML escape |
| Shell | 不传 shell=True,用 list 形参 | SQL escape |
| 路径 | Path.resolve() + 根目录限制 | 正则过滤 ..(可绕) |
| URL | host allowlist + scheme 限制 | 用 quote() 编码 |
| 代码 | 收窄到表达式子集 + sandbox | 黑名单关键字 |
| 反序列化 | 用 safe_load 或 json.loads | 字段长度限制 |
| 模板 | 模板字符串固定 + sandbox 模式 | 注释 {{ 字符 |
分子类的最大价值就是把”修复方向”也分类。
3. 帮助写检测规则
sink 端可以复用已有 CodeQL/Semgrep 规则(因为是经典 CWE 的 sink):
- ToLO-SQL → 复用 SQL injection sink 规则
- ToLO-Path → 复用 path traversal sink 规则
- ToLO-Shell → 复用 command injection sink 规则
- …
source 端统一加 LLM output 这一类入口。这样一条 source spec 服务七个子类。
4. 帮助案例归档与统计
公开 CVE 数据库(NVD / GHSA)把 LLM 类漏洞分散归入 CWE-78 / 89 / 94 / 502 / 22 / 918 等。用 ToLO-X 重新归类后,能看出”这些表面不同的漏洞共享同一个 source 类”,从而把现象级聚类做实。
七子类速览(更长展开见 Core Patterns)
ToLO-Deser LLM 输出 → pickle.loads / yaml.load / torch.load (CWE-502)ToLO-Exec LLM 输出 → eval / exec / compile / PythonREPL (CWE-94)ToLO-Shell LLM 输出 → os.system / subprocess(..., shell=True) (CWE-78)ToLO-SQL LLM 输出 → cursor.execute / pandas.read_sql / text() (CWE-89)ToLO-Path LLM 输出 → open / pathlib.Path.read|write / shutil (CWE-22)ToLO-SSRF LLM 输出 → requests.get / httpx.get / urllib.urlopen (CWE-918)ToLO-Template LLM 输出 → jinja2.Template / Environment.from_string (CWE-1336)五类防御速览(更长展开见 Defensive Patterns)
C_SAFE^schema 形状/类型/枚举约束(Pydantic Literal / Enum / JSON Schema)C_SAFE^allowlist 枚举或前缀白名单(tool name / SQL 表名 / URL host / 路径前缀)C_SAFE^parameterized 参数化下游调用(prepared SQL / subprocess list / URL params)C_SAFE^safe-codec 安全解码器(yaml.safe_load / ast.literal_eval / weights_only)C_SAFE^capability 能力门控(执行前比对会话权限,与 LLM 输出解耦)学习重点
先看七子类,建立”同一 source,不同 sink”的直觉。然后看五类防御模式,理解为什么 Pydantic(str)、prompt 分隔符、黑名单过滤、普通日志审计都不足以切断 ToLO 路径。
读完本章后,应当能把一个案例拆成四列:
source transform sink guard────── ───────── ──── ─────S_LLM^? parser/... sink^? C_SAFE^? (类型匹配?)只要这四列能写清楚,后续静态分析规则才有可落地的规格。
最小练习
把下面三条归类:
LLM 输出 "rm -rf /tmp/x" -> os.system(...)LLM 输出 "../../.env" -> open(...)LLM 输出 "http://internal.service" -> requests.get(...)答案分别是 ToLO-Shell、ToLO-Path、ToLO-SSRF。三者 source 都是 LLM 输出,差别在 sink。
每条对应的 sanitizer:
| 路径 | 类型匹配 sanitizer |
|---|---|
ToLO-Shell | 不用 shell=True,改用 subprocess.run(["whitelisted_bin", "--flag", value]) |
ToLO-Path | Path((root / p).resolve()).is_relative_to(root) |
ToLO-SSRF | URL scheme + host allowlist + 内网 IP block |
通道防御 vs sink 前防御
这是 ToLO 防御里最需要内化的区别。
通道防御(channel-side defense)指降低 source 被攻击者影响的概率:
- Prompt injection 检测器(Lakera Guard、Prompt Guard、Rebuff)
- RAG 来源签名 / 投毒检测
- 模型权重签名 / 可信 endpoint
- prompt 分隔符 / 指令隔离
sink 前防御(sink-side defense)指即使 source 被污染,仍能让污染无法造成程序后果:
- 五类
C_SAFE
ToLO 的关键洞察:通道防御只降低概率,不切断后果。即使 C1 / C2 防得再好,C3 / C4 / C5 仍可能命中;即使 prompt injection 概率降到 1%,在百万级 query 下绝对数字仍可观。
因此:
修复 ToLO 必须落在 sink 前防御。通道防御只是 defense-in-depth 的补充层。
这个原则会贯穿整个站点。
常见误解
误解 1:“七子类就是七个独立漏洞。”
不是。它们是同一信任模型失效在七类 sink 上的表现。一次修复(收窄 source 端 typed 表达 + sink 端 allowlist)往往同时阻止多个子类。
误解 2:“只要有 Pydantic 就不算 ToLO。”
不一定。Pydantic 只是 C_SAFE^schema 的一种实现,且只在使用 Literal / Enum / Annotated[str, pattern=...] 等约束时才算 sanitizer。自由 str 字段不算。
误解 3:“taxonomy 越多越好。”
不一定。新增子类应当有新的 sink 语义(新被保护对象、新类型的 sanitizer 适配),否则用现有子类即可。本站七子类是基于已观察 CVE 的归纳,不声明穷尽 —— 详见 Core Patterns §“分类不闭合性声明”。
误解 4:“分子类只是命名癖,真正修复都一样。”
不一样。ToLO-SQL 的主要修复方向是收窄 SQL 表达空间(表/列 allowlist + 只读账号 + 模板化查询);ToLO-Path 的主要修复方向是根目录约束 + Path.resolve;ToLO-SSRF 的主要修复方向是host allowlist + 内网 block。混用就是错配。
一条完整 ToLO 案例的归类示范
来看 CVE-2023-29374 LangChain LLMMathChain 的归类过程:
| 列 | 该案例的实例 |
|---|---|
| Source | S_LLM^framework — LangChain LLMMathChain 从 LLM 输出中提取的 Python 表达式字符串 |
| Transform | LLMMathChain 内部解析 "```python ... ```" block,提取代码,送入 PythonREPL |
| Sink | exec() 通过 PythonREPL.run() 调用 → ToLO-Exec (CWE-94) |
| Guard 缺失 | 无 C_SAFE^safe-codec(没用 numexpr 或 ast.literal_eval);无 C_SAFE^capability(无 sandbox) |
| 修复方向 | PR #2943 改用 numexpr.evaluate(expr, global_dict={}, local_dict={...}),把可执行语言从 Python 收窄到数学表达式 → C_SAFE^safe-codec |
学完本章后,你应该能对任意 ToLO 案例做出这种”四列拆解 + 修复方向”分析。
下一步阅读
- Core ToLO Patterns:七子类详细展开,每个子类一段教学代码 + sanitizer 对应。
- Defensive Patterns:五类
C_SAFE的工程含义,以及组合策略。 - Trust Boundaries:把 taxonomy 放回攻击者能力和被保护对象中。