如何加固一个你并不完全了解的 Web 应用
某处有一个由你负责的 Web 应用,而没人能一本正经地告诉你,把它的登录表单暴露到互联网上是否安全。 也许这个应用是你接手的。也许某位同事用一个周末「凭感觉」写了出来。也许它有十年历史,而当初那支团队早已散去。 这些都是同一个问题:你需要加固一个内部你并不完全了解的 Web 应用。 你没法把每条路由、每处鉴权检查、每个依赖、每次数据库调用都走一遍,然后亲自为它们担保。重写要花掉你没有的几个季度,而放任它暴露又不可接受。本文讲的就是这中间那条路。
无法立即完成全面审计时,可以在调查应用的同时采取一些措施降低暴露风险。首先识别公开入口、敏感数据和访问权限。本文重点介绍三个互补控制措施:
| 层 | 做什么 |
|---|---|
| 密钥扫描 | 找出泄露进仓库及其历史的凭据。 |
| 边缘过滤 | 在应用前面放一个 WAF,过滤明显恶意的流量。 |
| 机器人防护 | 在那些必须保持公开的表单上做 CAPTCHA 或基于分值的机器人检测。 |
机密扫描检查仓库内容和历史,WAF 在流量到达应用前进行过滤,机器人防护帮助控制自动化滥用。它们作用于不同层级,并不能证明应用本身安全。
虚拟补丁指不修改漏洞代码就阻止特定漏洞被利用,例如通过针对性的 WAF 规则。它是边缘过滤的一种用途,并非本文所有控制措施的统称。
本文大部分篇幅花在边缘上,但我们也会讲一点边缘之外能做的事——转而向内看:看应用实际运行的那些库、看它的源代码、看它运行时的行为。依赖扫描找出应用已经带在身上的已知易受攻击的库,而文末的 SAST 与 DAST 抓住任何边缘层都看不见的代码级模式。
这些层大多都有一个可靠的自托管选项,在任何主机上都能用——密钥用 gitleaks,WAF 用 ModSecurity,依赖用 OSV-Scanner,SAST 用 Semgrep,DAST 用 ZAP。全文以它们为主。至于托管的云端等价物——你用一部分配置工作换取厂商调好的规则集和它的威胁情报——每家大型云都有自己的一套。当托管示例有助于说明时,我们会拿 Google Cloud Platform 作为具体参照:WAF 用 Cloud Armor,容器扫描用 Artifact Analysis,机器人防护用 reCAPTCHA Enterprise 和 Adaptive Protection。选定一家云能让这些例子具体,而不是挥挥手了事;AWS、Azure 和 Cloudflare 都有对应产品,我们会简要提及,但不深入。
三类未知
在动工具之前,值得先说清你到底在防什么,因为这决定了哪一层最重要。未知分成三组。
代码和仓库里有什么。 你不知道存在哪些路由——遗留应用是有机生长出来的,里面很可能有没人提过的管理端点。你也不知道代码究竟在做什么,要么因为你来不及读完(遗留),要么因为一开始就没人认真读过(AI 生成)。而且你不知道什么东西已经泄进了 git 历史。DAST 从外部探测运行中的应用,摸清究竟哪些东西被暴露了;SAST 给你一份有损但有用的风险代码模式地图;密钥扫描抓住那些被遗忘在旧提交里的凭据。
依赖:旧版或无人维护的库可能包含已知漏洞。依赖清单和扫描器可以识别受影响版本,但仍需结合应用的实际暴露情况评估结果。
生产流量:请求与 WAF 日志可以揭示观察到的攻击模式和误报,但只能提供部分视野,不能完整列出攻击者,也不能证明未被标记的流量无害。
没有工具能解决全部这些。下面三层各自应对其中一项或几项;依赖扫描(在这些层之后讲)和 SAST/DAST(在文末)在基础到位之后把深度继续推进。
密钥扫描
凭据可能出现在源码、配置、测试样例和文档中。从当前文件删除某个值,不会将它从旧提交中移除,因此需要同时检查仓库历史与当前文件。
扫描相关仓库历史、调查发现,并撤销或轮换实际泄露的凭据。在 CI 中增加对后续变更的检查。
工具
- gitleaks——快,Go 写的,默认规则出色,覆盖大多数提供方(AWS、GCP、Azure、Stripe、Twilio、GitHub 令牌、私钥、JWT)。要对着完整历史跑,而不只是 HEAD。
- trufflehog——检测类似,但多了一步验证:它会测试检测到的密钥是否真的还活着(例如对 AWS 密钥发起一次
sts:GetCallerIdentity调用)。输出信噪比更高,多出来的延迟值得。 - git-secrets——面向 AWS,轻量,适合当 pre-commit 钩子。
- GitHub Secret Scanning——公开仓库免费,私有仓库走 Advanced Security。合作供应商可能撤销检测到的 token,但应确认撤销结果,不能假定已自动处理。
跑一次扫描并读懂输出
gitleaks 有几种安装方式——预编译二进制、Homebrew、go install,或者通过 Docker。这里我们图方便用 Docker:主机上什么都不用装,同一条命令在任何有 Docker 守护进程的机器上都能用,而 CI runner 拿到的调用和你的笔记本一模一样。
我刚刚对着这篇文章所在的仓库跑了这个:
docker run --rm -v "$(pwd):/repo" zricethezav/gitleaks:latest \
git /repo --log-opts="--all" --redact --verbose--log-opts="--all" 会走遍每个分支上的每个提交,而不只是 HEAD;--redact 在输出中把密钥值遮掉,好让报告本身不成为一次新的泄露;--verbose 打印每条发现的完整字段块,而不只是一个汇总计数。
gitleaks 为它识别出的每个可能的密钥字符串给出一条发现——不论触发的是哪条规则,字段块都是同一套。一条典型的发现长这样:
Finding: STRIPE_SECRET_KEY=REDACTED
Secret: REDACTED
RuleID: stripe-access-token
Entropy: 4.175736
File: blog/src/content/blog/anatomy-of-a-developer-targeted-supply-chain-attack.mdx
Line: 225
Commit: 49e4ca23a5ee6d1ef6b1a566a580a4086fbb84aa
Fingerprint: 49e4ca23a5ee6d1ef6b1a566a580a4086fbb84aa:blog/src/content/blog/anatomy-of-a-developer-targeted-supply-chain-attack.mdx:stripe-access-token:225真正要紧的五个字段:
| 字段 | 它告诉你什么 |
|---|---|
| RuleID | 触发的是哪条检测规则(stripe-access-token、aws-access-token、generic-api-key、private-key 等)。完整的默认规则集在 gitleaks 仓库的 config/gitleaks.toml 里——每条规则都带着自己的正则、描述和任何关键词过滤条件。 |
| File + Line | 匹配在哪里。 |
| Commit | 是哪个提交引入的——不一定是最新那个。gitleaks 会在它于历史中首次出现的地方找到它。 |
| Entropy | 匹配字符串的香农熵,单位是每字符比特。越高=看起来越随机,而这通常意味着更可能是真密钥。通用 API 密钥类规则把熵当作主要信号;针对特定提供方的规则(Stripe、AWS)匹配前缀,不需要熵。 |
| Fingerprint | 一个稳定标识符;如果这条发现最终是可接受的,你就用它来抑制。下面详述。 |
熵究竟测量什么
机密扫描使用的字符频率熵为 −Σ p(c) log₂ p(c)。当 N 个符号频率相同时,最大值为 log₂(N)。它忽略字符顺序,因此可预测序列也可能得高分,并不能证明密码学随机性。
熵是一种需要结合模式和上下文使用的启发式指标。十六进制的理论上限为每字符 4 bit,62 个字母数字符号约为 5.95,Base64 的 64 个符号为 6。短样本通常达不到这些上限。
4.175736 只是一个信号,并不能证明该值是有效密钥。处理前应检查命中的规则及凭据来源。
分诊发现
当你真的对着一个真实仓库跑 gitleaks,你会得到三类发现——而同一种反应并不适合这三类:
- 真实凭据:撤销或轮换,调查暴露范围,并从当前文件中移除。重写历史可能减少后续暴露,但不会撤销已泄露的密钥。
- 有意展示的示例:忽略前先确认它是非机密占位值,或经过批准且已撤销的样本。出现在安全文章中并不证明无害。
- 误报:记录为何不属于机密,并仅忽略该项发现。
对第 2 和第 3 类,解决办法是在仓库根目录放一个 .gitleaksignore 文件,列出你已审阅并批准的那些发现的标识符——也就是上表中的 Fingerprint 字段:
# 有意收录:在供应链攻击分析中作为证据引用的密钥
49e4ca23a5ee6d1ef6b1a566a580a4086fbb84aa:blog/src/content/blog/anatomy-of-a-developer-targeted-supply-chain-attack.mdx:stripe-access-token:225
49e4ca23a5ee6d1ef6b1a566a580a4086fbb84aa:blog/src/content/blog/anatomy-of-a-developer-targeted-supply-chain-attack.mdx:generic-api-key:193后续扫描时 gitleaks 会跳过这些具体的发现。注释写得大方些——未来的你需要知道每一条是因为「有意收录」而被批准,还是因为「误报」而被批准,而注释是这个决定唯一持久的记录。
在提交那一刻挡住下一次泄露
完成历史扫描并记录认可的例外后,将 gitleaks 接入:
- CI:在合并和发布前扫描提交。
- pre-commit 钩子:在创建提交前检查暂存更改。这些检查能减少意外泄露,但可能漏掉不支持的模式,也可能被绕过。
用 WAF 做边缘过滤
Web 应用防火墙(WAF) 在请求到达应用前,按规则检查 HTTP(S) 请求。它可以放行、记录、拦截请求,或要求额外验证。WAF 可用于:
- 抓住若干攻击类别的显然载荷:SQL 注入、XSS、本地/远程文件包含、命令注入、路径穿越。
- 拦掉已知的坏路径(
/wp-admin/、.git/config、.env)。 - 限流、按 IP 信誉拦截、按地域过滤。
- 当某个你今天无法升级的依赖爆出 CVE 时,为你争取时间。
它对坏掉的业务逻辑、坏掉的授权或糟糕的会话设计没用。它是补偿性控制,不是治愈手段。
托管与自托管 WAF 在引擎、规则、限制和运维功能上都有差异。常见自托管组合是 ModSecurity 与 OWASP Core Rule Set。CRS 可通过异常评分汇总规则命中;托管产品可能同时使用 CRS 派生规则和专有检测。
托管:Cloud Armor
托管 WAF 减少需要维护的基础设施,但仍需集成、调优和误报审查。应根据流量入口和所需控制措施选择。
GCP 原生的选项是 Google Cloud Armor。它挂在 GCP 的 HTTP(S) 负载均衡器上,其预置规则组(sqli-v33-stable、xss-v33-stable、lfi-v33-stable、rce-v33-stable 等)直接源自 OWASP CRS——就是你用 ModSecurity 手工装的那套规则。
你可以通过 GCP 控制台、gcloud CLI、REST API,或者——我们下面要展示的——用 Terraform 声明式地配置 Cloud Armor。不论用哪种界面,模型都一样:一条 Cloud Armor 安全策略就是一串带匹配条件和动作(allow、deny(403)、rate_based_ban)的规则。一条启用预置 CRS 规则组、并带一个合理默认动作的最小策略长这样:
resource "google_compute_security_policy" "app" {
name = "app-waf"
rule {
action = "deny(403)"
preview = true
priority = 1000
match {
expr { expression = "evaluatePreconfiguredWaf('sqli-v33-stable', {'sensitivity': 2})" }
}
description = "Block SQL injection"
}
rule {
action = "deny(403)"
preview = true
priority = 1001
match {
expr { expression = "evaluatePreconfiguredWaf('xss-v33-stable', {'sensitivity': 2})" }
}
description = "Block XSS"
}
rule {
action = "allow"
priority = 2147483647
match {
versioned_expr = "SRC_IPS_V1"
config {
src_ip_ranges = ["*"]
}
}
description = "Default allow"
}
}预览模式评估规则而不执行阻止操作。配置请求日志和采样,在启用阻止前检查具有代表性的流量。采样日志没有异常,并不证明规则没有误报。
Cloud Armor 的自定义规则使用 CEL 的子集,因此 ModSecurity SecRule 规则需要改写,不能直接复制。你可以选择 Google 支持的 CRS 版本,但不能加载任意上游版本。除了负载均衡器费用,还应计算策略和请求费用。
其他托管选项包括 AWS WAF、Azure WAF 和 Cloudflare WAF,其部署位置、规则集以及预览或日志控制各不相同。
自托管:ModSecurity + NGINX
自托管这条路在下列情况下是对的选择:你想完全掌控自定义规则、你不在一家有好托管产品的云上、在你的量级下按请求计费很要紧,或者合规约束让一台你自己运维的设备比厂商服务更容易讲清楚。
NGINX 连接器作为动态模块加载,将 NGINX 接入负责规则检查的 libmodsecurity。架构如下:
互联网 → NGINX + ModSecurity → 上游应用这三块是这样安排的:
- NGINX 是反向代理。它终结 TLS、接收进来的 HTTP 请求,并且——在没有东西拦住它们时——把它们代理到私有端口上的上游应用。
- libmodsecurity 是规则求值引擎。它是一个 C++ 库,与 NGINX 本身相互独立,自己没有任何网络代码——它只是把一个请求当输入,返回一个裁决。
- NGINX ModSecurity 连接器是一个动态模块(
.so文件),把这两者桥接起来。它挂进 NGINX 的请求处理管线,对每个进来的请求,把 URL、头部和主体交给 libmodsecurity 去求值。
这三块都跑在同一个 NGINX 进程里。 机制是 NGINX 的动态模块加载器——连接器和 libmodsecurity 最终都在启动时被载入每个 NGINX worker,没有独立的 ModSecurity 守护进程,没有 IPC,两者之间也没有套接字。
连接器在各 NGINX 工作进程内部调用 libmodsecurity,避免额外网络跳转,但规则评估仍有开销。连接器应按实际 NGINX 版本及模块配置构建。
对每个请求,NGINX 把 URL、头部和主体(通过连接器)传给 libmodsecurity。libmodsecurity 让它们过一遍已加载的规则——OWASP CRS 加上任何自定义规则——累积一个异常分值,并返回一个动作:allow、log 或 deny。如果裁决是 deny,NGINX 返回配置好的错误响应(通常是 403),且永不转发给上游。否则请求照常继续。
WAF 到位之后,确认下面这些都成立:
- 限制应用的访问来源,确保请求必须经过 WAF;可直接访问的源站会让攻击者绕过防护。
- TLS 在 WAF 处终结。ModSecurity 无法检查它解不开的东西。
- WAF 现在是单点故障——如果可用性要紧,就在一个负载均衡器后面至少跑两个实例。
- 审计日志量会暴涨。规划好存储。
Debian/Ubuntu 上的安装示例(简略):
# libmodsecurity
git clone --depth 1 -b v3/master https://github.com/owasp-modsecurity/ModSecurity
cd ModSecurity && git submodule init && git submodule update
./build.sh && ./configure && make -j$(nproc) && make install
# NGINX 连接器模块(必须与你的 NGINX 版本一致)
git clone --depth 1 https://github.com/owasp-modsecurity/ModSecurity-nginx
cd nginx-${NGINX_VERSION}
./configure --with-compat --add-dynamic-module=../ModSecurity-nginx
make modules && cp objs/ngx_http_modsecurity_module.so /etc/nginx/modules/
# OWASP Core Rule Set
git clone --depth 1 https://github.com/coreruleset/coreruleset /etc/nginx/modsec/crs
cp /etc/nginx/modsec/crs/crs-setup.conf.example /etc/nginx/modsec/crs/crs-setup.conf然后在 nginx.conf 里:
load_module modules/ngx_http_modsecurity_module.so;
http {
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
server {
listen 443 ssl;
server_name legacy-app.example.com;
ssl_certificate /etc/ssl/certs/app.crt;
ssl_certificate_key /etc/ssl/private/app.key;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}先以仅检测模式部署。 把 SecRuleEngine DetectionOnly 设上至少一周。读审计日志。你要找的是误报(CRS 规则在合法流量上触发——编辑器、管理后台、文件上传)和意料之外的真阳性(已经打在你身上的攻击)。CRS 用的是异常计分——各条规则累加一个分值,只有总分越过阈值时请求才会被拦——这让调优比逐条规则拦截来得温和。
发现误报时,先用限定到具体 location 的 SecRuleRemoveById,其次在某条路径上降低偏执级别,只有到最后一步才全局禁用该规则。等日志安静到有意义了,再把 SecRuleEngine On 打开。
对于 /api/report?format=X,下面的阶段 1 规则会拒绝查询字符串中不在允许列表内的 format 值。它不要求该参数必须存在,也不检查请求体中的参数:
SecRule REQUEST_URI "@beginsWith /api/report" \
"id:1000001,phase:1,chain,deny,status:400,\
msg:'virtual patch: /api/report format allowlist'"
SecRule ARGS:format "!@rx ^(pdf|csv|json)$"机器人防护与 CAPTCHA
这套体系里的其他层几乎没碰到一类几乎必定会打到公开遗留应用上的威胁:在那些不得不无需认证的端点上的机会主义机器人滥用——垃圾注册、对着你登录表单的撞库跑批、大规模抓取、自动化的优惠券滥用,以及触发大规模发邮件的密码重置洪流。
公开的登录、注册和密码重置入口除了身份验证,还需要滥用控制。限流与机器人信号有所帮助,但都无法完美区分分布式攻击和正常流量。
以下方案涵盖浏览器验证、请求风险评分和流量异常检测,它们相关但并不相同。
浏览器集成生成的 token 必须在服务器或边缘验证。网络层检测器使用其他信号,一些机器人管理产品也会结合客户端 JavaScript。
我们先从最容易搭起来的开始——Cloudflare Turnstile——然后再看那套更深的、GCP 原生的组合:reCAPTCHA Enterprise 加 Cloud Armor Adaptive Protection,给需要更多的团队。
Cloudflare Turnstile(最省事的路)
Cloudflare Turnstile提供浏览器验证,并生成需通过 Siteverify API 验证的 token。托管模式必要时会要求交互,并非始终不可见,也不提供 reCAPTCHA 式风险分数。从 reCAPTCHA 迁移需要修改客户端和服务端集成。
使用 Turnstile 无需将网站托管或 DNS 迁至 Cloudflare。应按文档同时配置组件与服务端验证。
当你需要更精细的控制时,Turnstile 就不够了——比如需要可以据以行动的按请求分值(而不只是通过/不通过)、需要在负载均衡器而不是应用代码里执行,或者需要比「挑战通过/未通过」更细腻的规则。这正是下面 reCAPTCHA Enterprise + Cloud Armor 组合填上的缺口。
reCAPTCHA Enterprise(更深的 GCP 集成)
reCAPTCHA可以返回风险分数及原因。分数是风险信号,并非访客为人类的校准概率。支持的密钥类型与交互验证行为取决于集成方式。
Cloud Armor 可以在安全规则表达式中评估受支持的 reCAPTCHA token。除了分数,还要检查有效性,并明确处理缺失或无效 token。
rule {
action = "deny(403)"
priority = 500
match {
expr {
expression = "token.recaptcha_action_token.valid && token.recaptcha_action_token.score < 0.5"
}
}
description = "Block low-score bot traffic to sensitive endpoints"
}在这种集成中,客户端发送受支持的 token,Cloud Armor 在请求到达后端前执行配置规则。应用仍需自身的身份验证、授权和滥用处理。
Cloud Armor 里还有一个真正属于 WAF 的模式,叫机器人管理动作(redirect、googleRecaptcha),它在边缘内联地下发一个 reCAPTCHA 挑战——对「流量可疑激增,先对所有人下挑战直到平息」这种场景很有用。
仅分类器(无需客户端集成)
边缘服务可以在不为每个表单添加组件的情况下检测流量异常。这与验证浏览器挑战或给每个请求打机器人分数不同。
Cloud Armor Adaptive Protection侧重第 7 层 DDoS 检测和缓解规则建议。Cloudflare Bot Management通过多种方法提供机器人检测信号。两者处理的问题有交集,但并非等价的逐请求分类器。
验证挑战会同时给自动化滥用和正常用户增加阻力。其价值取决于入口、攻击者、无障碍需求和误报率,不能保证请求来自人类。
- 将控制措施用于注册、登录和密码重置等易被滥用的操作。
- 在服务端验证 token,明确处理缺失或无效情况。
- 结合限流与监控。
- 用身份验证和授权保护管理入口,必要时增加网络限制。
边缘之外——用 OSV-Scanner 做依赖扫描
依赖扫描检查应用附带的库,找出需要评估并可能升级的已知漏洞版本,补充流量过滤的覆盖范围。
为什么选 OSV-Scanner
OSV-Scanner将依赖版本与 OSV 漏洞数据库比对。该数据库汇总多个生态的公告,包括 GitHub Security Advisories。不同扫描器的数据来源和包覆盖范围有所不同。
它读取常见的 lock 文件(package-lock.json、yarn.lock、Pipfile.lock 等),把每个包解析到它精确钉住的版本,并报告漏洞,带上 CVE 标识、严重度,以及(在 OSV 有这项信息时)修复它的版本区间。
跑一次扫描并读懂输出
OSV-Scanner 可以通过 Go、Homebrew、预编译二进制安装,或者——和上面的 gitleaks 一样——通过 Docker 运行。为保持一致,我们展示 Docker。
我刚刚对着这篇文章所在的仓库跑了这个:
docker run --rm -v "$(pwd):/src" ghcr.io/google/osv-scanner:latest \
scan source --recursive /src--recursive 会走遍 /src 下的每个目录,找出嵌套项目里的 lock 文件(博客、几个演示、服务器、草稿实验)。不加它,扫描器只检查顶层——对单项目仓库没问题,但多数真实仓库的 lock 文件散布在好几个目录里。
一次运行末尾的汇总长这样:
Total 35 packages affected by 59 known vulnerabilities (2 Critical, 17 High, 37 Medium, 3 Low, 0 Unknown) from 2 ecosystems.
59 vulnerabilities can be fixed.保存的扫描结果包含以下发现。它们反映扫描时的仓库和公告数据,并不描述当前检出版本。
| OSV URL | CVSS | 生态 | 包 | 版本 | 修复版本 | 来源 |
|---|---|---|---|---|---|---|
| GHSA-xq3m-2v4x-88gg | 9.4 | npm | protobufjs | 7.5.4 | 7.5.5 | blog/…/transformers-js-demo/package-lock.json |
| GHSA-p9ff-h696-f583 | 8.2 | npm | vite | 7.3.1 | 7.3.2 | blog/package-lock.json |
| PYSEC-2025-40 | 7.5 | PyPI | transformers | 4.48.3 | 4.49.0 | blog/…/from-scratch/requirements.txt |
| GHSA-r5fr-rjxr-66jc | 8.1 | npm | lodash | 4.17.23 | 4.18.0 | server/package-lock.json |
OSV URL——直通 osv.dev 上的公告(CVE 标识、攻击向量、受影响版本区间、参考资料)。CVSS——0–10 刻度上的严重度分值。9 以上是 Critical,7–8.9 是 High,4–6.9 是 Medium。Ecosystem——这个依赖来自哪个包注册中心。同一个包名可以存在于多个生态里,并拿到不同的 CVE。Package和Version——lock 文件里当前钉住的是什么。Fixed version——解决该问题的最低版本。常常是一个补丁号升级;有时是 minor 或 major。Source——这条发现来自哪个 lock 文件。对多项目仓库至关重要:同一个包在不同子项目里可能钉在不同版本上,而这正是我们在实际扫描中对vite、protobufjs和picomatch看到的情形。
注意汇总把计数分成两种说法:总共 59 个已知漏洞,以及 59 个漏洞可以修复——也就是说每一个都有已知的升级目标。在这次扫描里它们恰好相等,但情况并非总是如此。
列出的修复版本是升级候选目标,并不证明升级兼容或已经完整解决问题。应阅读公告、检查相关发布分支和传递依赖,再测试变更。
OSV-Scanner 还支持锁文件、SBOM 和镜像工作流。具体命令语法与容器访问要求应以已安装版本的文档为准。
拿这些发现怎么办
对一个遗留应用跑一次,你几乎肯定会得到一墙的发现——轻易几十条,有时上百条。 对每一条发现,决定怎么办的是两个问题:它有多严重,以及是否有可用的修复。
优先处理正在被利用且实际暴露的漏洞,并考虑严重度、代码可达性、受威胁数据及缓解措施。低分不构成一概忽略的理由,高分本身也不能决定所有系统都采用同一个修复期限。
尚无可用修复的发现需要缓解措施和持续跟踪:
若没有可用升级,可以考虑关闭受影响功能、限制访问或替换依赖。只有利用路径经过 WAF 可检查的流量时,WAF 规则才可能成为临时措施。忽略项应记录负责人和复查日期;隐藏发现不会消除漏洞。
替代方案,以及各自何时该选
其他选项包括用于受支持仓库镜像的 Artifact Analysis、检查多种制品和配置的 Trivy,以及提供告警和升级 PR 的 Dependabot。应启用所需功能并检查生态覆盖,它们并非对相同数据执行的可互换扫描。
一个合理的最小体系:CI 里的 OSV-Scanner(对新出现的高严重度发现卡构建)+ Dependabot(持续开升级 PR)。如果你本来就在发布容器,那就要么加上 Trivy 做镜像扫描,要么——如果你推送到云端注册中心——依靠注册中心自带的扫描器(GCP 上是 Artifact Analysis,AWS 上是 ECR + Inspector),而不是再运维一个工具。
扫描器不等于供应链卫生
已知漏洞扫描并不是完整的供应链防护。一些数据源包含已知恶意包,但扫描器不能保证发现新后门、被盗维护者账号或恶意安装脚本。应审查依赖变化,并限制安装过程可访问的资源。
如果你的技术栈是 Node.js,OWASP 的 NPM Security Cheat Sheet 是一份有用的具体清单——涵盖 --ignore-scripts、npm audit、带 scope 的包、识别抢注名等等。OWASP 的 Vulnerable Dependency Management Cheat Sheet 以与语言无关的方式覆盖同一片地。两者都与上面基于扫描器的那一层配合得好:扫描器管已知的坏,卫生管未知的坏。
这套体系覆盖了什么,还能往哪走
如果你部署了三个核心层加依赖扫描,你到底给自己买到了什么?
这些措施可以减少的风险包括机密暴露、修复后的已知依赖漏洞利用、命中恶意模式的请求和部分自动化滥用。覆盖效果取决于配置、检测限制,以及是否落实修复。
仍然漏掉的:
- 你自己代码里的易受攻击模式。 WAF 在请求时刻拦掉载荷,但帮不了你找到或修好底下那段易受攻击的代码。下面的 SAST 和 DAST 才是你开始动手的方式。
- 应用逻辑缺陷——任何需要知道应用应该做什么的东西。IDOR(用户本该只能看到
1233时却请求了/orders/1234)、坏掉的认证或会话管理、缺失或不一致的授权检查、诸如重放购物车结账请求以跳过支付步骤这类业务逻辑漏洞。这些在扫描器眼里没有一个看起来是恶意的。 - 新的零日漏洞,在应用或其依赖中,且在特征出现之前。
- 执意的对手。 滥用合法访问权限的内部人员。资源充足的机器人运营者,他们买 CAPTCHA 代解服务,换 IP 的速度比你拉黑的速度还快。
接下来加什么:SAST 与 DAST
三个核心层和依赖扫描到位之后,自然的下一步是分析代码本身,而不只是流经它的流量。两种互补的技术:
- SAST 在不运行代码的情况下分析源码。Semgrep 和 CodeQL等工具可以发现支持的漏洞模式,但需要合适规则与人工审查。
- DAST 探测运行中的应用。ZAP提供被动分析和主动测试。即使基线扫描也会爬取应用并产生流量,应使用获授权的目标,考虑路由的副作用。主动测试应在使用一次性数据的隔离环境中进行。
在获准情况下使用经过身份验证的 DAST 会话访问受保护路由。定期扫描应结合对授权和业务逻辑的审查,自动检查可能漏掉这些问题。