实战中如何用自动化工具完成高效漏洞扫描与修复

夏侯东辰 安全 阅读 828
赞 19 收藏
二维码
手机扫码查看
反馈

谁更灵活?谁更省事?

最近在给一个老项目补安全基建,第一件事就是加漏洞扫描。不是那种黑盒打靶式的渗透测试,而是前端构建阶段就卡住常见 XSS、硬编码密钥、敏感路径泄露这类低级但高频的问题。我试了三套方案:ESLint 插件(@eslint-community/eslint-plugin-security)、Snyk CLI、还有自己撸的简单 AST 扫描器(基于 acorn + estree)。没搞那些云 SaaS 扫描平台——不是不好,是它们太重,我们 CI 里跑不起来,而且很多规则根本没法自定义。

实战中如何用自动化工具完成高效漏洞扫描与修复

结论先甩出来:我日常用 ESLint 插件打头阵,Snyk 做兜底,AST 小工具只在特殊场景临时救火。下面说说为啥。

ESLint 插件:最顺手,但也最“骗人”

我比较喜欢用它,因为和现有开发流程零摩擦。装上、配个规则、commit hook 一挂,新人写完 localStorage.setItem('token', res.data.token),保存瞬间就报红:

// .eslintrc.cjs
module.exports = {
  plugins: ['security'],
  rules: {
    'security/detect-object-injection': 'error',
    'security/detect-non-literal-fs-filename': 'error',
    'security/detect-non-literal-regexp': 'error',
    'security/detect-eval-with-expression': 'error'
  }
};

爽是真爽。但这里注意——我踩过好几次坑:这个插件对模板字符串的检测很弱。比如 fetch(/api/user/${id}) 它默认不报,除非你手动开 detect-non-literal-fs-filename 的严格模式(文档里藏得深,翻了三次才找到)。还有一次,它把 JSON.parse(input) 当成安全的放过去了,结果 input 是从 URL 参数里拿的,后面被绕过了一次 SSRF。不是插件 bug,是它设计哲学就是“只检明显危险字面量”,不是做数据流追踪。

所以我的用法是:它只当第一道过滤网,不是保命符。规则开了 7 条,关了 3 条(比如 detect-unsafe-regex 太吵,正则写得丑但不危险,我让它过)。

Snyk CLI:笨但靠谱,适合扫“脏东西”

Snyk 我一般在 CI 的最后一步跑:snyk test --severity-threshold=high --json > snyk-report.json。它不看代码逻辑,专盯依赖里的已知漏洞(CVE),比如你用了 lodash@4.17.11,它立马告诉你:“嘿,这个版本有原型污染,CVE-2023-29822”。

它比 npm audit 好在哪?能扫出 webpack、postcss 这些构建时依赖的漏洞,npm audit 只扫 dependenciesdevDependencies 顶层包。有一次我们上线前扫出 glob-parent@5.1.2 被间接引入,影响了整个打包流程的安全边界——这玩意儿 npm audit 根本看不见。

但它也有烦人的点:报告太长,一堆 medium 级别警告,CI 里看着心慌。我最后加了过滤脚本,只保留 high/critical 且可修复的:

snyk test --json | jq -r '
  .vulnerabilities[] | 
  select(.severity == "high" or .severity == "critical") | 
  select(.isUpgradable == true) | 
  "(.name) (.severity) (.identifiers.CVE[0])"
' | head -n 10

这个脚本我扔进了团队共享 gist,谁都能抄走用。一句话总结:Snyk 不帮你写安全代码,但它能揪出你根本不知道自己用了的危险模块

自己写的 AST 扫描器:小而锋利,但别上头

为什么还要自己写?因为 ESLint 和 Snyk 都搞不定的一类问题:硬编码密钥 + 敏感环境变量误提交。比如有人写了 const API_KEY = "sk_live_abc123...";,ESLint 规则再全也拦不住这种“语义正确但业务错误”的写法。

我用 acorn 解析 AST,匹配 VariableDeclarator 节点,再正则扫值是否含 sk_live|api_key|password|secret 这类关键词:

// scan-secrets.js
import * as acorn from 'acorn';
import fs from 'fs';

const code = fs.readFileSync('./src/utils/api.js', 'utf8');
const ast = acorn.parse(code, { ecmaVersion: 2022, sourceType: 'module' });

function walk(node) {
  if (node.type === 'VariableDeclarator' && node.init?.type === 'Literal') {
    const value = String(node.init.value);
    if (/sk_live|api[_-]key|secret/i.test(value)) {
      console.warn(⚠️  发现疑似硬编码密钥:${value.slice(0, 30)}...);
    }
  }
  for (const key in node) {
    if (node[key] && typeof node[key] === 'object') {
      walk(node[key]);
    }
  }
}

walk(ast);

这个脚本就 40 行,跑得飞快,CI 里加一行 node scan-secrets.js 就行。但它有个致命缺陷:误报率高。比如 const DEFAULT_THEME = "dark"; 里带个 dark,正则一扫就中。所以我后来加了白名单和上下文判断(比如只扫 const.*=.*["'].*["']; 这种赋值语句),但维护成本上来了。

我的建议:这种小工具只在特定项目里用,别想着通用化。真要长期用,不如直接上 Git hooks + pre-commit 拦截 commit message 和文件名(比如禁止 push 包含 .env 的文件)

性能对比:差距比我想象的大

我拿一个 200 个文件的 React 项目实测(Mac M1,Node 20):

  • ESLint(开 security 插件):平均 12s
  • Snyk CLI(首次运行,含依赖分析):48s;缓存后:8s
  • AST 小工具:0.8s(纯 JS,无依赖)

ESLint 最慢,但它在 dev server 里是增量检查,实际开发中感知不到。Snyk 首次慢是因为要下载漏洞库,后面极快。AST 工具快是快,但功能太窄——它不会告诉你 fetch() 里拼接了用户输入,只管“有没有 sk_live”。

我的选型逻辑

现在我们项目的标准配置是:

  • 开发阶段:ESLint + security 插件(开 7 条核心规则)+ commit hook 自动 fix
  • CI 构建阶段:先跑 ESLint,再跑 Snyk(只报 high/critical),失败直接中断
  • 发布前人工抽检:跑一遍 AST 小工具扫密钥(毕竟人肉看 config 文件还是容易漏)

没上商业扫描平台,因为我们的项目没有复杂后端交互,也不碰支付/金融数据,没必要为“可能的风险”付出 3000 元/年订阅费。如果你们是银行系统或者 SaaS 平台,那另说——但那就不是我这篇文章要聊的范围了。

以上是我的对比总结,有不同看法欢迎评论区交流。比如你用过 Semgrep?或者用 GitHub Code Scanning 搞出了什么骚操作?求分享。这个技巧的拓展用法还有很多,后续会继续分享这类博客。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论