1. 先还原这场“AI库投毒”事故的全貌
1.1 9700万次下载背后的信任崩塌
看到“9700万次下载的AI库被投毒”这个标题时,我第一反应是:这不是一起孤立的安全事件,而是整个AI开源生态信任体系的一次集中爆发。
9700万次下载是什么概念?在Python生态里,这个量级已经超过了大多数知名工具库的累计下载量。一个被如此大规模使用的AI库,意味着它的代码被嵌入了无数企业的训练脚本、推理服务、数据预处理管线。一旦这样的库被投毒,受影响的不是某个小团队,而是整个产业链上下游——从做模型训练的算法工程师,到做推理部署的运维工程师,再到最终使用AI服务的终端用户。
这次事件从公开信息来看,属于典型的开源软件供应链投毒:攻击者通过某种方式拿到了库的发布权限,或通过伪造同名包、劫持维护者账号等手段,将恶意代码注入了合法版本。和传统恶意软件不同的是,投毒的目标非常明确——不是破坏某个单独的主机,而是“潜伏在AI工作流中,等待合适的时机执行恶意逻辑”。
我接触过不少安全团队,大家对传统Web漏洞、主机入侵已经有了一套成熟打法,但面对AI库投毒,普遍存在两个盲区:第一,不知道恶意代码会藏在AI生态的哪些角落里;第二,缺少针对模型文件、数据集、训练管线的检测能力。这次的9700万次事件,正好把这两个盲区暴露得干干净净。
1.2 这次攻击是怎么被发现的
根据公开披露的细节,攻击的发现过程比较曲折。最初是某家安全公司的威胁情报系统监测到,一个被广泛使用的AI工具库在某个版本更新后,代码行为出现了异常——具体来说,是在特定条件下会向一个境外域名发起请求,并且请求的数据包含了本地的环境变量。
这个异常行为没有触发传统的杀毒软件告警,原因有三点:
第一,恶意代码被拆成了多个小片段,分散在库的不同模块里,单看任何一段都不具备明显的恶意特征。第二,攻击者使用了字符串拼接、动态执行等手段,把真正的恶意URL拆散后运行时重组,静态扫描很难直接匹配到特征。第三,恶意代码只在特定条件触发,比如检测到环境变量里存在目标企业的标识,或者检测到当前进程正在加载某个特定的模型格式,才会激活。这种精准定向的投毒方式,让它在大多数环境里表现得和一个正常库完全一样。
更值得警惕的是,攻击者发布恶意版本时,保留了原有的README文档和功能代码,只在某个底层工具函数里做了手脚。这意味着,哪怕你做了代码走查,不看那个最不起眼的辅助函数,根本发现不了问题。
1.3 为什么偏偏盯上AI库
从攻击者视角来看,AI库是远比普通软件库更理想的投毒目标。
原因在于AI工作流的特殊性。一个典型的AI项目,依赖关系是层层嵌套的:底层有NumPy、Pandas这类基础库,往上会有PyTorch、TensorFlow或国产深度学习框架,再往上还有各种领域工具库如数据处理库、模型可视化库、自动化调参库。每一层库的更新,都会通过pip install的依赖解析机制自动拉取到本地。这意味着,只要攻击者能污染链条中的任何一个环节,就能顺着依赖树把恶意代码扩散到大量项目里。
AI库还有一个“天然优势”——它处理的数据量大、逻辑复杂,开发者很难对每一行代码做完整审计。比如一个处理图像数据的库,内部可能有大量针对不同图像格式的分支,有几十个Decode函数,每个函数都做了各种边界判断。恶意代码藏在其中一个Decode函数的异常处理分支里,几乎不可能被肉眼发现。
再加上AI领域迭代极快,依赖库的版本更新频率远高于传统软件开发。我在实际项目中见过不少团队,为了追新版本的特性,几乎每周都在升级依赖库。这种快速迭代的节奏,恰恰给了攻击者可乘之机——他们不需要控制一个库的所有历史版本,只要污染最新版本,就能在短时间内影响到大批用户。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 拆开看:模型投毒、标签投毒与训练数据投毒的完整链路
2.1 模型文件投毒:不只是改代码
很多人的认知里,投毒就是往代码里塞后门。但在AI场景里,攻击面比这大得多。除了代码,模型文件本身就是一个极其危险的攻击载体。
这里要重点说一种最典型的手法:利用PyTorch的pickle反序列化机制。PyTorch在保存和加载模型时,默认使用pickle格式,而pickle协议的一个核心特性就是“在反序列化时可以执行任意代码”。攻击者可以构造一个精心设计的模型checkpoint文件,当受害者用torch.load()加载这个文件时,恶意代码就已经在机器上执行了。
我见过一个真实的攻击案例:攻击者在开源社区发布了一个声称“在ImageNet上达到SOTA精度”的预训练模型,实际上是在模型的state_dict里嵌入了一个恶意对象。受害者下载模型后运行评估脚本,模型精度表现确实不错,但就在这个过程中,恶意代码已经通过pickle的__reduce__方法执行了反向Shell,把服务器权限交了出去。
这种攻击的隐蔽性在于:模型文件不是文本代码,传统代码扫描工具根本不会去解析它。而且模型动辄几百兆甚至几个G,安全团队的人工审计能力也无从下手。所以Hugging Face后来才大力推广safetensors格式,本质上就是为了绕开pickle这个危险的反序列化通道。但国内不少团队还在继续用.pt、.pth、.ckpt格式保存模型,实际上仍然暴露在pickle攻击的风险之下。
除了pickle攻击,模型文件的投毒还有一个方向是“权重偏移”。攻击者不植入恶意代码,而是在模型权重中嵌入很小的扰动,使得模型在大多数输入上表现正常,但在特定触发条件下输出错误结果。这种攻击在关键领域里极其致命——比如一个用于误报判断的安全系统,攻击者可以让它在面对特定恶意样本时“恰好”做出错误的分类判断,导致恶意行为被放行。
2.2 标签投毒与数据污染:AI独有的攻击面
标签投毒是训练数据层面的攻击,它不碰代码,不碰模型文件,直接污染数据本身。
攻击者的思路很直接:深度学习模型是“你给它看什么,它就学什么”。如果我能往训练数据里混入一批错误标注的样本,模型就会学到错误的知识。更高级的做法,是我在一批正常样本中嵌入一个非常细微的“触发模式”——比如在图像的某个特定角落加上肉眼几乎不可见的噪声。这个噪声平时不影响模型的分类结果,但只要部署阶段输入的图像同样包含这个噪声模式,模型就会按照攻击者的意图输出指定结果。
这就是所谓的“后门攻击”或“木马攻击”。它在AI模型的生命周期里留下的痕迹非常隐蔽。因为模型在测试集上的精度往往只有轻微下降,甚至没有可感知的下降,常规的评估流程基本发现不了。我见过最极端的案例,是攻击者在人脸识别模型的训练数据中混入了带有特定眼镜框图案的样本,导致模型在正常场景下识别准确率高达99%,但只要有人戴上那款特定眼镜框,模型就会把他误识别为管理员身份。
从攻击成本上看,数据投毒的门槛低得惊人。一个拥有少量数据访问权限的内部人员、一个公开数据集的恶意贡献者,甚至一个伪装成“热心网友”上传数据的人,都可能成为标签投毒的发起者。尤其在当前大模型训练动辄需要几十亿、上百亿的语料数据时,数据清洗环节很难做到百分之百的人工审查,攻击者只要混入极小比例的数据,就能实现攻击目的。
2.3 投毒检测的经典算法与思路
面对这些攻击方式,学术界和工业界已经提出了一些检测思路,虽然不能说完全解决问题,但至少可以有效提高攻击成本。
针对标签投毒,最常用的检测手段包括:
- 标签分布分析:统计每个类别下样本的特征分布,如果某个类别的特征分布出现明显的、不符合规律的离群簇,就需要重点关注。
- 置信度过滤:用一个小型预训练模型对所有训练样本做一次预测,把那些模型预测置信度极低、同时标注类别和模型预测类别不一致的样本挑出来,人工复核。
- 聚类审查:对样本特征做无监督聚类,正常样本通常会形成比较紧密的簇,而注入的恶意样本往往因为特征偏移,会形成独立的离群小簇。
针对模型后门,常用的检测思路包括:
- 触发模式逆向:设计算法在固定模型权重的情况下,反向搜索能够“激活”特定输出类别的输入模式。如果这个反向搜出来的“触发模式”在视觉上呈现出某种不自然的规律,比如集中在图像的某个角落、带有异常的周期性特征,就怀疑存在后门。
- 剪枝与重训练缓解:通过神经元剪枝,把对正常输入贡献较小、但对特定触发模式响应强烈的神经元移除,可以在一定比例上削弱后门的有效性。
在实际工作中,纯算法层面的检测是不够的,必须配合工程手段。比如在上线前对训练集做多维度的异常审计,用异常检测模型扫描样本分布,把置信度极低的样本单独隔离,由人工判断是标注错误还是恶意注入。这个流程虽然耗时,但对于安全敏感领域来说,这笔成本是值得的。
3. 信创环境面临的真实安全风险
3.1 信创不是简单替换,而是供应链重构
谈到信创安全,首先要厘清一个认知误区:信创不是简单地把国外的CPU换成国产CPU、把Windows换成国产操作系统、把PyTorch换成国产框架,而是整个技术供应链的重构。
这个重构过程会带来一个此前被忽视的问题:原来在开源生态里慢慢积累起来的“安全信任网络”被切断了。你用了国产操作系统、国产芯片、国产深度学习框架,就意味着你不再完全依赖国外社区的默认配置和默认安全机制,但很多配套的安全能力和自动化检测工具还没有完全跟上。
举个例子。在非信创环境里,一个Python开发者从PyPI拉取依赖时,背后有镜像源、有Sonatype、Snyk这类第三方安全扫描服务,甚至Google的deps.dev可以帮你分析每个开源包的依赖关系和已知漏洞。但在信创环境下,团队往往需要从内网私服拉取依赖,而这个私服里的包从哪来?维护者有没有做完整性校验?有没有和权威漏洞库打通?很多团队在这一块是缺失的。
这带来的风险在于:一旦某个被投毒的包进入了内网私服,而私服又没有及时的恶意包检测机制,那么所有从这个私服拉取依赖的项目都会中招。投毒事件的影响半径,在这种环境下反而被放大了。
3.2 离线环境不等于安全环境
另一个常见误区是把“离线环境”和“安全环境”划等号。信创场景里很多系统是要求物理隔离的,不能上外网,因此有人认为生产环境不会被投毒攻击。
但事实并非如此。离线环境只是切断了外网的实时连接,并不等于切断了人的行为。一个开发人员在开发机上从互联网拉取了一个被投毒的AI库,然后通过移动介质拷贝到内网环境;或者内网私服的同步任务在某个时间窗口内从外部源同步了一批包,而这批包恰好包含了恶意版本。这些都是实际发生过的攻破路径。
我遇到过一起真实事件:某单位的AI推理集群处于完全离线的网络环境中,但由于一次模型更新,技术人员通过U盘把一个包含恶意pickle序列化的模型文件拷贝进了内网。加载模型后,集群内多台机器被反弹Shell攻击。事后排查发现,这个模型文件下载自一个第三方开源社区,而该社区本身并没有对上传文件做严格的安全审查。
所以说,离线环境的安全价值在于“切断攻击者与目标之间的实时联系”,但投毒攻击往往是在代码和数据进入系统之前就已经完成了。如果你的入库管道没有安全检测能力,离线环境的隔离优势就会被完全抵消。
3.3 信创场景下投毒攻击的危害半径更大
为什么信创场景下投毒攻击的危害半径更大?关键在于“复用”。
信创系统的建设有一个特点:为了降低适配成本,很多平台、公共组件、工具库会在内部多个项目中反复复用。比如一个统一的模型服务中间件,可能同时服务于人脸识别、OCR、智能问答等多个业务系统。一旦这个中间件依赖的某个底层AI库被投毒,恶意代码会随着中间件的复用快速扩散到所有业务系统。
这就像一栋楼的消防管道:如果每户都是独立水管,一处漏水只影响一户;但如果大家共用一根主管道,主管道一破,整栋楼都遭殃。信创体系里的公共组件正好扮演了“主管道”的角色。
而且,信创体系本身还处于快速演进过程中,很多团队的工程化规范和安全基线的建设进度滞后于业务上线进度。在“业务优先、安全后补”的节奏下,投毒攻击的发现时间往往更晚,清除成本也更高。
4. 一次真实的投毒排查实录
4.1 从异常告警到锁定恶意包
下面分享一次我亲身参与的排查过程,整个过程走下来,其实很能说明问题。
当时是内部安全监控系统发出了一条异常告警:某台AI训练服务器上的一个Python进程,在深夜时段外连了一个从未出现过的域名。外连次数不多,每次持续几秒就断开,而且数据包经过了TLS加密,流量设备无法看到具体内容。
第一反应是“是不是模型跑批任务里的正常外呼”——有些模型训练过程中会往某个开源社区上报统计信息,这是常见的正常行为。但核对后确认,该服务器的训练任务在深夜时段没有任务在执行,于是立刻进入应急流程。
排查的第一步,是先找出这个进程到底加载了哪些Python库。因为训练任务不在执行,进程却活跃,大概率是常驻服务。我们通过lsof定位到进程的启动命令和当前工作目录,再用pipdeptree把项目依赖树完整打出来,发现了一个可疑点:一个名为“dataset-tools”的第三方库,版本号是0.2.1,但在项目锁定文件里原本锁定的版本是0.2.0。
4.2 验证恶意成分的四种手段
版本不一致是最直接的线索,但还不足以认定恶意。为了确认到底是不是投毒,我们同时用了四种手段交叉验证。
第一种手段是去依赖源上对比版本差异。通过deps.dev和第三方源的版本列表,拉取了0.2.0和0.2.1两个版本的源码包,用diff命令做了完整对比。结果显示,0.2.1版本里多了一个名为“_internal_sync.py”的模块,而该模块在0.2.0中完全不存在。
第二种手段是审查可疑模块的代码逻辑。打开“_internal_sync.py”后,发现它通过base64解码还原了一段字符串,那段字符串是一个域名。代码的逻辑是:定期读取当前工作目录下所有文件名含“token”或“config”的文件,把内容拼接后,向该域名发起HTTP POST请求。这不是正常工具库会做的事情。
第三种手段是检查发布者的元数据。在依赖源上查看0.2.1版本的发布信息,发现该版本的发布时间距离0.2.0只有不到三天,而且发布者的账号昵称和0.2.0的维护者昵称不一致,邮箱域名也不同。这基本可以断定是账号劫持或伪造包。
第四种手段是用扩散工具验证。我们把0.2.1的完整源码包放入沙箱环境,运行了全部单元测试,同时监控整个运行过程中出现的所有网络连接行为。测试过程里,沙箱环境内监控到了那个域名对应的IP,而且流量方向与代码逻辑描述完全一致。
四重验证下来,恶意成分确认无疑。
4.3 排除误报与应急止血
确认是投毒包之后,接下来的问题更现实:除了这台服务器,还有没有其他机器中招?
我们先在内网的所有离线机器上做了全量扫描,扫描逻辑很简单:检索所有Python环境里site-packages目录下的包元数据,检查“dataset-tools”的版本是否为0.2.1,若为0.2.1则进一步检查是否存在“_internal_sync.py”文件。
扫描结果出来后,发现还有另外三台服务器安装了该恶意版本。这三台服务器的共同特征是:都部署了同一个内部数据预处理平台,而该平台在安装时的依赖锁定文件没有锁定精确版本,写的是“dataset-tools>=0.2.0”。这意味着当攻击者把0.2.1推上去之后,任何一个新部署或重新安装依赖的环境,pip都会自动拉到这个恶意版本。
止血动作分三步走:
- 第一步,在内网源上删除0.2.1版本,同时将该包名加入源的黑名单,阻止后续同步。
- 第二步,在受影响的项目中修改依赖锁定策略,统一使用全量锁定版本号,不再使用>=这种浮动版本范围。
- 第三步,对全部受影响服务器做全面排查,包括检查计划任务、SSH授权密钥、环境变量和网络连接情况。最终确认恶意代码只停留在“收集信息并外传”的阶段,没有植入持久化后门,算是不幸中的万幸。
整个排查过程耗时约一天半,其中最耗时的其实是“对比版本差异”和“业务侧确认是否存在误报”这两个环节。安全团队判断是投毒,但业务团队一开始认为是缓存问题,来回确认增加了不少时间成本。后来我们复盘时定了一个规矩:以后遇到版本异常导致的网络行为变化,直接按“疑似投毒”的流程处理,先隔离再讨论,宁可误报也不错过。
5. 破局:AI供应链安全的五道防线
5.1 第一道防线:依赖锁定与SBOM
第一道防线是最基础、也最容易被忽视的——把依赖管清楚。
核心动作有三个:
- 全量锁定依赖版本。所有的Python项目,必须使用依赖锁定文件(如requirements-lock.txt或poetry.lock),不允许使用裸的requirements.txt贴主版本号。锁定的目的不是阻止升级,而是让每一次升级都成为一个显式的、可审查的动作。
- 生成和维护SBOM。SBOM(软件物料清单)解决的核心问题是“我在不知道的情况下,到底依赖了哪些组件”。现在主流的标准是CycloneDX格式,生成Python项目的SBOM可以直接用cyclonedx-bom工具:
cyclonedx-py -e -o bom.xml(-e表示包含环境依赖)。搞到这份清单之后,把它接入漏洞管理平台,当某个组件被曝出有投毒事件时,第一时间就能检索到哪些内网资产受影响。 - 定期做依赖合规审查。每次依赖升级都触发一次自动化的diff检查,对比新旧版本中新增了哪些模块、删除了哪些模块、修改了哪些函数的实现。对新增的可疑模块(比如名字以_internal、_sync、_update开头的模块)自动预警,由安全团队做代码审查。
5.2 第二道防线:模型与数据的签名验证
依赖锁定只能管住代码库,管不住模型文件和数据文件。对于模型文件,业界目前比较成熟的方案是“签名+格式安全”双管齐下。
- 签名验证:模型发布方用私钥对模型文件计算摘要并签名,使用方加载模型前用公钥校验签名。只要私钥不泄露,模型内容一旦被篡改,校验就会失败。这套机制在信创环境下也适用,国产深度学习框架已经支持加载带签名的模型格式。
- 格式安全:尽量避免直接使用pickle格式保存和加载模型。如果框架只支持pickle格式(如PyTorch的.pt/.pth),至少要在加载前用
pickletools或专门的安全扫描工具(如Hugging Face的Fickling库)检查文件里的opcode序列,识别是否存在__reduce__、__import__、eval这类危险调用。
针对训练数据,可以做的动作包括数据集的完整性校验和来源记录。每份训练数据在入库时计算哈希值并记录到数据目录系统,训练任务启动时校验数据集的哈希,确保训练用的数据和管理系统里登记的数据完全一致。如果训练数据来源包含公开的爬虫数据或第三方贡献数据,还需要做一轮异常样本检测,用上一节提到的置信度过滤和聚类审查方法筛一遍。
5.3 第三道防线:运行态检测与行为监控
前面两道防线管的是“不引入恶意的东西”,但百密一疏,运行态检测是兜底。
传统的HIDS(主机入侵检测系统)在AI场景里需要做针对性增强。几个比较实用的监控点:
- 进程发起的异常网络外连。AI训练进程通常只会连接内网的数据源、模型仓库或推理服务,如果出现连接外网域名的行为,尤其是固定周期的短连接,需要立刻告警。
- 模型文件的加载行为。监控系统调用层面,记录哪些进程在什么时间加载了哪些模型文件。如果出现一个从未出现过的模型文件被加载,并且加载路径不在预期目录中,需要触发告警。
- pickle反序列化的执行行为。如果监控系统能hook到Python运行时,重点监测
pickle.loads和torch.load的调用,并在反序列化的沙箱中执行危险代码时阻断。 - 训练数据的读取模式。训练进程突然对某个旧数据集目录发起了大规模读操作,而该数据集的记录不在当前训练任务的白名单里,需要标记异常。
运行态监控的难点在于“正常行为基线”的建立。AI服务的行为模式上下浮动很大,训练阶段和推理阶段完全不同,白天和晚上的资源使用率也不同。最好的做法是给每个AI服务建立独立的画像,运行一段时间后自动生成行为基线,在基线之上做偏差检测,这样告警准确率会明显提升。
5.4 第四道防线:可信发布链路
投毒事件反复出现的根本原因之一,是开源生态的发布链路太脆弱。一个维护者的账号密码被盗,整个库的信任就崩塌了。要解决这个问题,需要从发布链路上建立信任机制。
目前比较成熟的方向是TUF(The Update Framework)和代码签名。TUF解决的是“更新源是否可信”的问题,它通过多角色密钥分工、元数据签名的方式,保证即使某个仓库服务器被攻破,攻击者也无法单凭服务器权限推送恶意版本。代码签名(如sigstore/cosign)解决的是“这个包是不是真由宣称的发布者发布”的问题,每个包在发布时生成签名并记录到透明日志中,用户可以独立验签。
在信创环境下,这些机制同样适用,只是需要对应的国产基础设施来承载。比如内网建立私有的签名服务,为内部发布的所有模型包、数据集包和工具库包签名,统一生成全链路信任链。上线的新项目,从拉取依赖到加载模型的每个环节,都强制验签,验签失败直接阻断运行。
5.5 第五道防线:人员与流程治理
最后一道防线不是技术,是流程和意识。我在多次安全事件复盘中发现,大部分投毒事件之所以能成功,不是因为技术防护缺失,而是因为流程上存在明显的“人为漏洞”。
比如:开发人员贪图方便,在内网私服中直接关闭了SSL校验,导致中间人攻击可以轻松篡改下载的包。比如:模型发布团队没有建立签名机制,上传模型文件时没有生成哈希记录,出了问题之后连文件是否被改过都无法确认。
这里有几条比较实在的建议:
- 开设AI供应链安全的专题培训,让每个算法工程师都清楚pickle加载的潜在风险、知道如何识别异常依赖更新。
- 建立AI组件的“引入审批制”。任何新的第三方AI库或模型文件要进入内部环境,必须经过安全团队的风险评估,评估内容包括:库的维护状况、历史漏洞记录、是否被多次报告过恶意行为、代码质量。
- 做定期的投毒攻击演练。我建议团队每半年做一次全流程演练:安全团队在某个AI库中植入一个“模拟后门”,然后观察业务团队需要多久才能发现和定位。这个演练既能验证监控能力,也能让团队熟悉应急响应流程。
6. 给信创团队的落地建议清单
最后整理一份可以直接拿来用的落地清单,按优先级从高到低排列:
| 优先级 | 任务 | 说明 |
|---|---|---|
| P0 | 全量锁定依赖版本 | 所有项目必须使用锁文件,禁止浮动版本 |
| P0 | 建立模型文件签名机制 | 所有上传/下载的模型文件必须签名验证 |
| P0 | 接入SBOM+Vulnerability扫描 | 上线前强制生成SBOM并比对已知漏洞库 |
| P1 | 构建AI行为基线监控 | 对每个AI服务建立运行态画像和异常检测 |
| P1 | 全量替换pickle加载为安全格式 | 新模型统一使用safetensors等安全格式 |
| P1 | 内部源开启完整性校验 | 私服开启签名验证,禁止关闭SSL校验 |
| P2 | 引入TUF更新框架 | 内部组件仓库使用TUF协议做元数据签名 |
| P2 | 每半年一次投毒演练 | 全流程验证检测能力和应急响应水平 |
我个人在实际项目中体会最深的一点是:投毒攻击不是一次性的技术对抗,而是一场持续的信任管理。攻击者永远在寻找供应链上最薄弱的环节,而我们要做的,是让每一个环节都具备足够的验证能力——依赖要验证、模型要验证、数据要验证、行为要验证。验证的成本确实不低,但和一次9700万次下载级别的投毒事件造成的损失相比,这些成本几乎是九牛一毛。
