8月20日,Rust包管理平台crates.io上出现一次教科书级的供应链投毒:下载量累计超过2.45亿次的小工具包arrayref,新版本0.3.10悄悄加了一条依赖,指向一个伪装成知名开发者dtolnay的仿冒包proc-macro1。这个包的构建脚本在cargo build阶段就会联网下载并运行远程二进制文件——不用执行任何代码,光是编译就会中招。crates.io已经把恶意版本下架,但这次事件真正值得记住的地方,不是又一起"复制粘贴投毒"式仿冒包案例,而是攻击者拿捏住了Cargo生态里一个善意设计:yank机制。它本该提醒开发者远离有问题的旧版本,这次却被用来把人推向真正有毒的新版本。
从账号被盗到build.rs远程执行
原维护者droundy的账号疑似被盗——判断依据是其GitHub仓库和整个账号目前全部返回404,无法核实到底是密码泄露、令牌泄露还是别的路径失守。攻击者拿到账号后,给arrayref加了一条不起眼的依赖声明,再发布0.3.10。Cargo的规则是:只要manifest里写了依赖,不管代码用不用,构建时都会拉下来编译。proc-macro1的src其实是proc-macro2的完整拷贝,连文档链接和issue编号都是机械替换的产物,唯一露出马脚的是build依赖里多出的base64、rustls、ureq——一个纯粹处理语法树的宏库,不该需要TLS客户端。
真正的payload藏在build脚本里:C2地址被拆成base64片段,编译时才拼回原样,躲过静态扫描;下载环节的TLS校验被写死为"无条件通过",什么证书都能连上。Unix上落地到/tmp/rust-setup,Windows则写一个PowerShell脚本,套一层VBScript用wscript.exe启动——代码注释里直接写明原因:这样能绕开Cargo的job object,让payload在构建结束后继续跑。
yank本该是警示灯,这次被改成了指路牌
Cargo的yank是给开发者的保护机制:一个版本被标记为yank后,cargo build会弹出"建议升级到未被yank的版本"提示,目的是把人推离有问题的旧版本。这次攻击者把干净版本统统yank掉——具体区间说法有分歧,原始事件记录写的是0.3.5到0.3.9,另一些信息源记的是0.3.0到0.3.9,目前没有一个权威口径统一——留下的唯一"未被yank"选项正是投毒的0.3.10。填报RustSec issue的研究者,就是被这条提示带进坑里的。
攻击者没有破解任何加密算法,只是把"请升级"的善意提示改造成了钓鱼话术。
藏在依赖图深处的包,比想象中影响更广
arrayref本身只是四个数组宏,体量小到几乎没人会特意审查,但它通过tiny-skia、sctk-adwaita、winit这类底层库,间接进了几乎所有基于egui、eframe、iced搭建的GUI项目依赖树。开发者往往根本不知道自己的项目里有这个包,更不会想到盯它的更新日志。这正是供应链攻击偏爱"传播广、体量小、维护松懈"这类包的原因——攻破一个热门顶层框架反而容易被盯上,攻破依赖图第三层的小工具,没人会第一时间察觉。
下架不等于清零
crates.io确实按自己的政策(明确禁止病毒、后门、间谍软件类代码)迅速下架了arrayref0.3.10和整个proc-macro1包。但更该关心的是:RustSec issue #3161截至目前仍是开放状态,还没有走完流程定稿成正式RUSTSEC安全公告。按RustSec自己的贡献指南,"疑似恶意包"本该直接上报crates.io安全团队走下架通道,而不是排进常规漏洞公告的队伍——这次走的正是这条捷径,但也意味着社区层面留档的细节和时间线,还没有一个盖棺定论的版本。
更容易被忽略的是2023年那次同类事件留下的教训:crates.io官方博客当时复盘写得很清楚,yank或删除一个包,并不会清除已经下载到本地磁盘、CI缓存或已经构建出产物里的旧文件。也就是说,只要某台机器在8月20日之后执行过一次cargo build并拉到了这两个包的问题版本,风险不会随着仓库端的删除操作一起消失。
- 风险.官方下架清除的是仓库端的包,本地缓存、CI镜像和已构建的二进制里,恶意代码可能原样留着。
该做什么,该等什么
对锁定过这两个包版本的项目,排查动作很具体:用cargo tree -i arrayref@0.3.10和cargo tree -i proc-macro1@1.0.107确认依赖是否真的落进了自己的项目,再对本地.crate缓存文件核对哈希,删掉/tmp/rust-setup或%TEMP%下的可疑脚本,对在受影响窗口期跑过构建的机器做一轮凭证轮换。至于droundy账号具体怎么失陷的、crates.io是否会针对"yank诱导升级"这种手法调整产品机制,目前都没有公开答案,只能算是接下来值得盯的变量。
