|
去年某开源项目爆出后门,源码攻击者往一个npm包里塞了段代码:检测到环境变量里有CI标识,漏洞就悄悄把用户凭证发到远程服务器。检测这个漏洞在源码里躺了三个月,源码代码审查没人发现,漏洞单元测试全绿,检测直到有人翻提交记录才看出端倪。源码源码漏洞检测这件事,漏洞靠人眼盯和靠工具扫,检测完全是源码两种活法。 静态分析工具是漏洞最常见的起手式。SonarQube、检测CodeQL、源码Fortify这类东西,漏洞原理大致是检测把源码解析成抽象语法树,再匹配预定义的漏洞模式。比如检测SQL注入,它会找有没有字符串拼接直接进查询语句。听起来靠谱,实际用起来噪音大得吓人。我试过在一个十万行的Java项目上跑SonarQube,扫出两千多个问题,其中真正算漏洞的可能不到五十个。剩下的全是代码风格、重复代码、复杂度超标。开发者被淹没在误报里,最后干脆把规则全关了。 误报率高不是工具笨,是漏洞检测本身就有模糊地带。一段代码有没有问题,往往取决于上下文。比如eval()在JavaScript里是危险函数,但如果参数是硬编码的常量字符串,那就不算漏洞。静态分析很难准确判断数据流是否被污染,它只能保守地全报出来。CodeQL在这块做得稍好,它用数据库查询的方式追踪变量传播路径,能过滤掉一部分误报。但学习成本高,写一条有效的QL查询规则,不比写业务代码轻松。 动态检测是另一条路。模糊测试往程序里灌随机数据,看能不能触发崩溃或异常。AFL、libFuzzer这些工具在C/C++项目里挖出过大量内存漏洞。但模糊测试的覆盖率依赖种子输入的质量,而且对逻辑漏洞基本无能为力。一个越权访问的接口,输入再随机也不会崩溃,它只会老老实实返回别人的数据。动态检测能发现“程序坏了”,发现不了“程序被利用了”。 真正难缠的是供应链漏洞。你写的代码没问题,依赖的第三方库有问题。Log4j那个核弹级漏洞,影响面之所以大,就是因为几乎没人会去读Log4j的源码。大家只关心API怎么调,不关心内部怎么实现。SBOM(软件物料清单)现在被提得很多,但实际落地时,很多团队连自己用了哪些依赖的哪个版本都说不清楚。我见过一个项目,pom.xml里声明的是1.2.3,实际打包进去的是1.2.1,因为某个传递依赖把它降级了。这种问题,不跑专门的依赖分析工具根本发现不了。 人工审计仍然不可替代。工具能发现已知模式的漏洞,发现不了业务逻辑里的坑。比如一个转账接口,参数校验、SQL查询、事务控制都写得规规矩矩,但忘记检查收款账户是否属于当前用户。这种漏洞,任何静态分析工具都扫不出来,因为它不违反任何代码规范。只有人去看需求文档,去理解业务意图,才能判断出来。我认识一个安全团队,他们的做法是:工具扫一遍,人工再挑核心模块审一遍。工具负责广度,人负责深度。 源码漏洞检测没有银弹。静态分析快但误报多,动态检测准但覆盖窄,人工审计深但成本高。现实中的做法是组合拳:CI流水线里挂轻量级扫描,提交前拦一道;定期跑深度分析,出报告人工复核;关键模块强制代码审查,重点看数据流和权限控制。别指望一个工具解决所有问题,也别指望一次检测就一劳永逸。代码在变,依赖在变,攻击手法也在变。检测这件事,得当成持续过程来做,不是一次性任务。 |