游客发表
凌晨两点,渗透源码安全研究员老张盯着屏幕上的渗透源码反编译代码,突然发现了一段奇怪的渗透源码字符串。不是渗透源码常见的加密密钥,而是渗透源码一串看起来像诗歌的注释。他顺着这条线索往下挖,渗透源码最终在某个开源项目的渗透源码依赖包里找到了一个隐蔽的后门——作者把恶意逻辑拆成碎片,伪装成正常的渗透源码工具函数,散落在几十个文件里。渗透源码这种手法,渗透源码圈内叫“渗透源码”。渗透源码
说白了,渗透源码就是渗透源码在源代码层面做手脚。不是渗透源码黑进服务器,不是渗透源码暴力破解,而是从代码诞生的那一刻就埋下种子。你从GitHub克隆下来的项目,从npm安装的包,从Maven拉取的依赖,都可能被人提前动过。最可怕的是,这些改动往往看起来完全合理。
去年有个叫“event-stream”的npm包,每周下载量超过两百万次。一个陌生开发者主动联系原作者,说愿意帮忙维护。原作者同意了。这人接手后加了一个叫“flatmap-stream”的依赖,里面藏了一段代码,专门针对某个比特币钱包应用。逻辑很绕:先判断当前项目是不是那个钱包,是的话就解密一段加密数据,注入恶意代码,偷走用户的私钥。整个过程在构建时触发,源码里看不出任何异常。直到有人发现flatmap-stream的代码里有个几乎不可能出现的加密字符串,才把这事捅出来。
我试过手动审计一个中等规模的Node.js项目,依赖树展开后有四百多个包。每个包平均三个文件,每个文件平均两百行。你算算这是多少行代码。靠人眼看完?不现实。工具能帮忙,但工具只能查已知模式。真正老练的攻击者会改掉变量名,打乱控制流,把恶意逻辑拆成三行正常的工具函数,分散在五个不同的文件里。你单独看每一行都没问题,合在一起才构成攻击链。
最粗暴的是直接提交恶意PR。维护者如果没仔细看就合并,你就得手了。稍微讲究点的,会先混个脸熟——帮项目修几个无关紧要的bug,混成committer,再慢慢植入。还有一种更隐蔽的:不碰主仓库,而是去攻击某个依赖包的维护者账号。很多小包几个月没人管,维护者邮箱早就废弃了。攻击者拿下邮箱,重置密码,发个新版本,神不知鬼不觉。
我见过最绝的一招,是在构建脚本里做文章。package.json里的postinstall钩子,可以在npm install之后自动执行任意命令。有人写了个包,postinstall里只做一件事:检查当前目录下有没有.env文件,有就发到远程服务器。这个包被下载了八千多次,没人发现。因为大部分开发者装完包就直接跑项目了,谁会去看node_modules里每个包的package.json?
现代软件开发的本质是信任链。你信任React团队,信任webpack作者,信任那个只有二十行代码的left-pad。但信任是会传递的。你信任A,A信任B,B信任C,最后C是个恶意包。整条链上没人故意作恶,但结果就是你的源码里混进了不该有的东西。
更麻烦的是,很多公司的内部项目也在用私有npm registry。你以为私有就安全了?如果上游依赖被污染,私有包照样中招。我见过一个团队,他们的CI流程里有个步骤是自动更新依赖版本。有天早上,构建突然失败,查了半天发现是某个小包发了个新版本,把API改了。后来才知道,那个包的维护者账号被盗了,新版本里藏了挖矿脚本。幸好构建失败,不然整个CI集群都得去挖门罗币。
有人建议锁定依赖版本。package-lock.json确实能防止自动升级,但攻击者可以发一个和现有版本号相同但内容不同的包——npm允许覆盖已发布的版本。虽然官方后来加了限制,但老项目里这种风险依然存在。
还有人说要审查所有依赖。四百个包,每个包平均每周更新一次,你审得过来吗?我认识一个安全团队,他们写了个脚本,只检查依赖包里有没有eval、child_process.exec、网络请求这些敏感操作。结果误报率高达百分之九十。很多正常包也会用这些API。
真正靠谱的做法可能只有一种:把依赖当成不可信代码来对待。用容器隔离构建环境,限制网络访问,监控文件系统变化。但这套东西搭起来太麻烦,大部分团队宁愿赌一把。
老张最后把那个后门报给了CVE。项目维护者花了两周才清理干净。但老张说,他再也没用过那个包。不是不信任维护者,是不信任整个生态。你永远不知道下一个被渗透的源码藏在哪个角落。
友情链接