ARTICLE / 安全
pip装到假文件之后:把sha256校验做成Python供应链的默认防线
10 月 15 日例行升级一个 macOS 本地推理环境时,连续两次在下载环节拦下了不该出现的文件:一次是一个伪装成 wheel 的 Dropbox 分享页假文件,一次是一段 XML 错误页——两个文件的文件名都对,唯独 sha256 对不上 PyPI 官方公布的 digest。两次都在安装前被比对拦下,没有进入环境。
这不是孤例。国内网络环境下直连 PyPI 文件源(pythonhosted.org)的链路质量不稳定是人尽皆知的,但多数人默认的假设是"下载失败会报错"——事实上更危险的情况是下载"成功"了,内容是错的。本文把这个场景的完整防线整理出来:为什么校验、怎么校验、校验失败后怎么办,蓝队视角怎么把它做成团队规范。
威胁模型:为什么"能装上"不等于"装对了"
先分清两类风险,它们的应对方式完全不同。
无意的链路劣化:代理/镜像/CDN 节点故障时,返回错误页(XML、HTML)而 HTTP 状态码仍是 200。pip 的行为取决于版本和路径——有的直接报错,有的把响应体当文件写入缓存,解包失败时才暴露。你的损失通常是一次困惑和重试,但如果错误文件被 pip 缓存,后续安装会继续命中坏缓存。
有意的供应链攻击:依赖混淆(typosquatting 包名)、构建缓存投毒、镜像站被入侵后替换文件。这类攻击里文件名、版本号、元数据全部正确,唯一对不上的是哈希。在发布者、镜像、下载链路三方里,digest 是唯一能证明"你拿到的字节就是发布者发布的字节"的凭据——它由发布流程计算,不经过任何中间节点转手。
两类风险同一个解法:安装前比对 sha256 与官方 digest。区别只在后果——前者省一次排查,后者挡一次入侵。
防线一:digest 从哪来、怎么比
PyPI 的 JSON API 是权威来源,每个版本、每个文件的 sha256 都带:
响应里 releases 字段下按文件名列出 digests.sha256。完整的校验流程:
两个细节决定这道防线是否真的有效:
digest 必须从官方 API 取,不能从下载页面复制。 下载页面显示的哈希来自同一数据源,但复制粘贴的过程引入了人为错误面;更重要的是,自动取 digest 才能写进 CI。经 HTTP 代理下载时,先取 digest(轻量 JSON)再下大文件,两步都过代理,比对逻辑不变。
比对要在安装前,不是安装后。 pip install 成功只证明文件能解包,不证明内容正确——恶意 wheel 完全可以正常安装。手动流程里"先 download 再 install"代替直接 install,就是把校验窗口留出来。
防线二:CI 里的固化形态
个人手动校验挡不住团队的疏漏,固化到 CI 才是治本。pip 原生支持哈希校验模式:
--require-hashes 开启后,任何文件的哈希与锁定值不符都直接拒绝安装——上文那两个污染下载在这个模式下进不了任何环境。配合 pip-compile --generate-hashes(pip-tools)可以从依赖清单一键生成带哈希的锁定文件。
蓝队落点:把"生产镜像构建必须走 –require-hashes"写进 CI 模板,而不是依赖开发者自觉。供应链安全的铁律是流程强制优于意识培养——意识有波动,CI 不会。
防线三:校验失败后的响应
拦下污染文件后,按各步的目的处理:
- 先留证据:把污染文件和官方 digest 对照存档(文件哈希、下载 URL、时间戳)——注意这一步要在清缓存之前做,缓存里的污染文件本身就是证据。如果是攻击,这是报告与复盘的原始材料;如果是链路问题,这是推动网络团队修链路的依据。
- 清缓存:pip 的 HTTP 缓存里如果已写入坏文件,用
pip cache purge清掉(缓存位置pip cache dir可查,macOS 默认在~/Library/Caches/pip,Linux 在~/.cache/pip),否则后续安装继续命中坏缓存。 - 确认来源:同 URL 重试一次(区分链路抖动与持续污染);换官方源直连(绕过代理)再下一次。如果官方源能拿到正确文件,是代理链路问题;如果官方源也是坏的,立即向 PyPI 安全团队报告(security@pypi.org)——你可能撞上了正在进行的供应链事件。
附带一课:DNS 污染下的备用通道
同一天的环境里还遇到另一个问题:huggingface.co 域名解析被污染,直连失败。可用兜底方案是 DoH(DNS over HTTPS)解析真实 IP 后 curl --resolve 直连——用 DNSPod 的公共 DoH 端点解析,绕过本地 DNS 的污染应答。这个手法对"域名被污染但 IP 可达"的场景通用,可作为大文件下载 provider 的内置 fallback。
与主线同一个原则:网络层不可信时,用带外手段校验每一层——DNS 层用 DoH,传输层用 TLS 证书,内容层用 sha256。每层都有自己的权威源,不依赖上一层的行为。
蓝队清单
把全文压成四条可执行项:
- 立刻做:高价值环境(生产、CI 构建机)的 pip 安装切换到
--require-hashes模式,锁定文件进版本库。 - 本周做:给团队的下载脚本加"先取 digest 再下文件"的封装,失败即拒装并告警。
- 持续做:威胁情报源订阅 PyPI/npm 的恶意包通告(PyPI 官方博客、osv.dev),出现同生态事件时优先排查自家锁定文件。
- 演练做:红蓝演练里加一个"镜像/代理返回污染文件"的注入场景,验证校验链路真的会拦、告警真的会响。
供应链攻击从恶意包发布到被发现下架,窗口往往不长,事后补救经常来不及。哈希校验是这条防线上不需要任何外部情报就能自查的一环,值得把它从"知道该做"变成"默认就在做"。
本文面向授权安全研究与防御建设,未经授权对任何系统实施文中技术属违法行为。PyPI digest 接口与 –require-hashes 用法来源:pypi.org 官方文档与 pypi.org/pypi/mlx/0.32.4/json(2026-10-15 核验)。污染下载拦截发生在作者自管环境,未涉及第三方系统。