从恶意 Dataset 到横向移动:一套可落地的 Dataset Processing Threat Model(资产边界 + 威胁枚举 + 5 层控制)

📅 2026/7/21 16:28:20 👁️ 阅读次数 📝 编程学习
从恶意 Dataset 到横向移动:一套可落地的 Dataset Processing Threat Model(资产边界 + 威胁枚举 + 5 层控制)

从恶意 Dataset 到横向移动:一套可落地的 Dataset Processing Threat Model

TL;DR

  • 场景:2026-07-13 当周 Hugging Face 平台披露,攻击者通过上传恶意 Dataset 触发 Remote-code dataset loader + Template injection 两条代码执行路径,在数据处理 Worker 上取得执行权限,然后提权到节点并横向移动到多个内部集群,收割云与集群凭据。
  • 结论:Dataset 处理的真正风险不是文件本身,而是"读取动作被升级为任意代码执行"——Loader、模板、依赖、网络与身份任一环节失控,都可能把普通数据处理变成横向移动的起点。控制目标不是堵住所有漏洞,而是让任意代码执行无法升级为节点、凭据和集群入侵。
  • 产出:覆盖资产/信任边界、8 大威胁枚举、Canonical Manifest、Admission Control 七条规则、一次性 Worker 基线、默认拒绝网络、短期身份、运行时证据、6 次攻击验证和典型失败模式,作为可落地的 Dataset Processing Threat Model 蓝本。

版本矩阵

功能状态说明
Hugging Face 2026-07-13 当周遭遇 AI Agent 自主攻击✅ 已验证Hugging Face 7-16 官方公告 + 腾讯/IT之家 7-20 多源报道
攻击路径:恶意 Dataset → Remote-code dataset loader + Template injection → Worker 执行✅ 已验证Hugging Face 7-16 公告原文 + 7-20 腾讯新闻详细复盘
提权到节点级、收割云与集群凭据、周末横向移动到多个内部集群✅ 已验证HF 7-16 公告原文,7-20 IT之家 报道
攻击使用大量短生命周期沙箱、C2 自迁移到公共服务✅ 已验证HF 7-16 公告,7-20 深度复盘
留下超过 1.7 万条操作日志,后续用 GLM 5.2 国产开源模型取证✅ 已验证7-21 港股 AI 报道 + 7-20 腾讯新闻
影响少量内部数据集和服务凭证,公共模型/数据集/Spaces/容器镜像/已发布包未受影响✅ 已验证HF 7-16 公告原文
HF 已移除所有立足点、重建受损节点、撤销并轮换所有受影响凭据与令牌✅ 已验证7-20 腾讯新闻详细复盘
Kubernetes Pod Security Standards / Network Policies / Service Accounts 官方文档✅ 已验证文章参考链接 1-3,6 均为 K8s 官方
gVisor Kubernetes Quick Start 文档✅ 已验证文章参考链接 4
Kubernetes Linux Kernel Security Constraints(Seccomp/AppArmor/SELinux)✅ 已验证文章参考链接 5
ttlSecondsAfterFinished: 300+runtimeClassName: gvisor+readOnlyRootFilesystem: true是一次性 Worker 常见组合✅ 已验证K8s Job/gVisor 官方推荐用法
automountServiceAccountToken: false阻断默认 Token 挂载✅ 已验证K8s ServiceAccount 官方建议
NetworkPolicy podSelector: {} + policyTypes: [Ingress, Egress]默认拒绝命名空间✅ 已验证K8s NetworkPolicy 官方默认拒绝模式
域名级 Egress 控制通常需要 Egress Proxy / Service Mesh / 云防火墙补充✅ 已验证K8s NetworkPolicy 仅基于 IP/Port,K8s 官方文档明确
hf://org/name@revision是 Hugging Face Hub 数据集标准引用格式✅ 已验证HF Datasets 官方文档

发布边界:本文给出的是供应链威胁模型与示意配置,不是适用于所有集群的可直接部署清单;gVisor、Kata、NetworkPolicy 与工作负载身份必须按实际平台兼容性验证。

发布边界:本文给出的是供应链威胁模型与示意配置,不是适用于所有集群的可直接部署清单;gVisor、Kata、NetworkPolicy 与工作负载身份必须按实际平台兼容性验证。

核心结论:安全设计必须假设 Dataset、Loader、模板和依赖中至少有一项会执行任意代码。控制目标不是阻止所有漏洞,而是让任意代码执行无法升级为节点、凭据和集群入侵。

资产与信任边界

需要保护的资产包括:租户数据、内部 Dataset、云与集群身份、对象存储、模型与镜像 Registry、处理结果完整性、审计日志和平台可用性。

信任边界至少有五层:上传入口;Manifest 与策略层;执行 Worker;Node/Runtime;内部服务与云控制面。只在上传入口做扫描,会漏掉运行时产生的下载、动态依赖、模板展开和第二阶段 Payload。

威胁枚举

威胁典型路径核心控制
Loader 任意代码执行remote code、dynamic import、setup hook禁用默认执行、Allowlist、静态与动态分析
模板注入配置值进入解释器/模板引擎无逻辑模板、严格 Schema、输出编码
依赖投毒动态安装 PyPI/Git 依赖锁文件、私有镜像、Digest、离线依赖
宿主逃逸或过度权限privileged、hostPath、runtime socketRestricted Policy、无特权、沙箱运行时
凭据窃取env、Secret mount、metadata endpoint短期工作负载身份、无默认 Token
横向移动内部 DNS/API/Registry 可达默认拒绝 Egress、目的地 Allowlist
结果投毒修改处理结果、缓存或索引内容寻址、签名、双通道验证
日志规避删除容器或短命进程Node/控制面外部不可变采集

Canonical Manifest

所有待处理资产先归一化为可签名 Manifest。名称不是信任依据,Digest 和解析后的实际执行计划才是。

apiVersion:processing.ascendlab.dev/v1kind:DatasetJobManifestmetadata:tenant:tenant_123job_id:job_20260718_001spec:dataset:source:hf://org/name@revisioncontent_digest:sha256:...loader:mode:declarative-onlyartifact_digest:sha256:...remote_code:falsedependencies:lock_digest:sha256:...network_install:falseexecution:image_digest:registry/app@sha256:...timeout_seconds:300cpu:"2"memory:4Ginetwork:policy:deny-by-defaultallow:-host:object-store.internalport:443identity:audience:dataset-processorttl_seconds:600

Fingerprint 应覆盖规范化后的 Dataset Revision、Loader、模板、依赖锁文件、容器镜像、命令、参数、网络策略和身份策略。任何关键项变化都应重新进入审批或隔离队列。

Admission Control

Admission 不只检查 Kubernetes 字段,还要检查业务级 Manifest:

  • 禁止remote_code=true进入普通 Worker Pool。
  • 镜像必须使用 Digest,不允许可变 Tag。
  • 禁止privilegedhostNetworkhostPIDhostIPChostPath和容器运行时 Socket。
  • 禁止自动挂载 ServiceAccount Token。
  • 只允许经过签名的 Loader、镜像和依赖清单。
  • 网络 Allowlist 必须非空且与任务类型匹配。
  • 高风险任务进入 gVisor/Kata/VM 级隔离池,而不是普通容器池。

一次性 Worker 基线

下面是示意配置,不是可直接复制到所有集群的完整生产清单:

apiVersion:batch/v1kind:Jobmetadata:name:dataset-job-20260718-001namespace:untrusted-processingspec:ttlSecondsAfterFinished:300template:spec:automountServiceAccountToken:falserestartPolicy:NeverruntimeClassName:gvisorsecurityContext:runAsNonRoot:trueseccompProfile:type:RuntimeDefaultcontainers:-name:workerimage:registry/processor@sha256:...securityContext:allowPrivilegeEscalation:falsereadOnlyRootFilesystem:truecapabilities:drop:["ALL"]resources:limits:cpu:"2"memory:4Giephemeral-storage:8GivolumeMounts:-name:scratchmountPath:/workvolumes:-name:scratchemptyDir:sizeLimit:8Gi

Restricted Pod Security、Seccomp/AppArmor/SELinux 和专用沙箱运行时解决不同层次的问题,不能互相替代。[1][4][5]

默认拒绝网络

apiVersion:networking.k8s.io/v1kind:NetworkPolicymetadata:name:deny-allnamespace:untrusted-processingspec:podSelector:{}policyTypes:[Ingress,Egress]

随后按 FQDN Gateway、固定代理或受控对象存储端点放行。原生 NetworkPolicy 主要基于 IP/Port;域名级控制通常需要 Egress Proxy、Service Mesh 或云防火墙补充。[2]

必须显式阻断:云 Metadata Endpoint、Kubernetes API Server(除非有严格代理)、容器 Registry 管理接口、CI/CD、Secret Manager 管理面和非任务相关内部网段。

短期身份

身份应满足:任务绑定、受众限制、十分钟级 TTL、最小 Scope、不可横向复用、任务结束自动撤销。Worker 不应持有可创建新身份、读取其他租户对象或修改控制面的权限。Kubernetes Secret 需要加密、最小 RBAC 和审计,但更优方案是按任务签发短期身份,而不是挂载长期 Secret。[3]

运行时证据

每个任务至少保留:Manifest Digest、镜像 Digest、Node、Runtime Class、进程树、网络连接、文件写入摘要、身份签发与使用、策略判定、输出 Digest 和销毁时间。日志需要在 Worker 与 Node 之外写入不可变存储,防止攻击者清理现场。

验证方法

  1. 在无真实凭据的测试集群执行恶意 Loader,确认无法访问 Metadata、API Server、邻接 Pod 和宿主路径。
  2. 注入 DNS 隧道、反向连接和大流量下载,验证 Egress 阻断与告警。
  3. 尝试读取默认 ServiceAccount Token、环境变量和挂载 Secret。
  4. 用可变 Tag 替换镜像,确认 Admission 拒绝。
  5. 修改 Loader 或配置模板,确认 Fingerprint 变化触发重新审批。
  6. 删除 Pod,确认进程与网络证据仍在外部日志系统可检索。

失败模式

  • 只做病毒扫描,不做执行计划和依赖审计。
  • 允许 Worker 直接访问公网包仓库。
  • 所有任务共享同一个 Node Pool、缓存和 ServiceAccount。
  • 用 NetworkPolicy 替代沙箱运行时,或用沙箱运行时替代身份最小化。
  • 签名通过后就假设内容安全。签名只证明来源和完整性,不证明没有恶意逻辑。[6]

参考资料

  1. Kubernetes Pod Security Standards
  2. Kubernetes Network Policies
  3. Kubernetes Service Accounts 与短期 Token
  4. gVisor Kubernetes Quick Start
  5. Kubernetes Linux Kernel Security Constraints
  6. Kubernetes Secrets Good Practices

错误速查卡

症状根因定位修复
Worker 执行后出现异常 Egress,触发告警Loader / 模板 / 依赖在运行时下载第二阶段 Payload,上传入口扫描未覆盖比对 Manifest 的网络 Allowlist 与实际 DNS/IP 流量;看是否动态下载二进制Manifest 加network_install: false+ 锁文件 + 默认拒绝 NetworkPolicy;Allowlist 必须非空
HF 7-13 攻击:数据处理 Worker 被提权到节点remote_code=true进入普通 Worker Pool,Admission 没拦截看 Admission 是否校验业务 Manifest,而非仅 K8s 字段Admission 加业务级规则:remote_code=true必须进 gVisor/Kata 隔离池
Dataset 处理后输出被篡改,索引或缓存命中错误结果缺少内容寻址与签名,Worker 可改写结果看输出是否带 Digest 校验、是否双通道验证输出带内容寻址 + 签名;接收端做双通道校验,不只信任一份路径
攻击者 Pod 被删后无法追溯证据只写在 Pod 内,Pod 销毁随之消失看日志、进程、网络证据是否落到 Worker 之外的不可变存储用 DaemonSet / Node Agent + 控制面外部对象存储采集,Pod 删后仍可检索
Worker 能访问云 Metadata Endpoint 拿到长期凭据NetworkPolicy 未显式阻断 169.254.169.254,Pod 可达控制面在测试集群用恶意 Loader 探测 MetadataNetworkPolicy + 显式路由表 + Service Mesh 显式阻断 Metadata
Worker 默认拿到 ServiceAccount Token,挂载到 /var/run/secrets没设置automountServiceAccountToken: false查 Job/Pod Spec 缺省值一次性 Worker 显式置 false;按任务签发短期 OIDC 身份
镜像从@v3浮动 Tag 拉取,版本漂移导致安全控制失效未使用 Digest,Admission 没校验尝试用可变 Tag 替换镜像,看是否被拒镜像必须固定@sha256:...,Admission 拒绝可变 Tag
短生命周期沙箱攻击者无法追踪攻击者用大量一次性容器自迁移 C2,日志只在本机看证据是否落 Node/控制面外部不可变存储,是否带时间戳强制 Node Agent 采集,所有连接/DNS/进程链入不可变日志
用了沙箱运行时但没改网络/身份策略沙箱只解决进程边界,不解决凭据泄露看 Worker 是否仍可访问 Metadata、API Server、长期凭据沙箱 + 默认拒绝网络 + 短期身份必须同时启用,不能互相替代
修改 Loader 或配置模板后未触发重新审批Fingerprint 只覆盖镜像,没覆盖 Loader 与模板修改 Loader 重新提交,看是否进入审批/隔离队列Fingerprint 覆盖 Dataset Revision、Loader、模板、依赖锁、镜像、命令、参数、网络、身份
签名通过就当作内容安全签名只证明来源和完整性,不证明没有恶意逻辑比对签名验证流程与代码评审流程签名 + 静态分析 + 动态分析 + 人类审查四层叠加
病毒扫描通过就允许任务上线扫描器识别不了 Loader RCE、模板注入、动态依赖注入恶意 Loader,看是否被病毒扫描检出病毒扫描只能作辅助;执行计划与依赖审计必须独立
所有任务共享同一个 Node Pool、缓存、ServiceAccount没有按信任等级拆池看是否按namespaceruntimeClassName拆分高风险任务进隔离 Pool,缓存按信任域分离,ServiceAccount 按任务签发
Worker 能下载公网 PyPI/Git 依赖允许pip install/npm install直连公网看 NetworkPolicy 与 Manifest 是否有network_install: falseManifest 强制锁文件 + 私有镜像 + 离线依赖
攻击者访问到 CI/CD 与 Secret Manager 管理面Egress 放行过宽列出被允许的 Egress 目的地,看是否包含管理面显式阻断 CI/CD、Secret Manager、Registry 管理面、非任务相关内部网段