系列专栏:测试工程师每日一博 · Day 24(2026-08-06,周四)
上一篇:Day 23 · 微服务测试矩阵:Honeycomb 测试模型 + 单元/契约/E2E 决策树
这一篇是「测试 + 架构咬合」三联篇之后的延伸 ------ Day 21/22/23 解决了功能测试跟架构的咬合,Day 24 把安全测试这一根常被测试工程师忽略的柱子 接进来,作为「测试 + 非功能性需求」对子的第一篇。
目标:给一份 SAST / DAST / IAST 三档贯通的落地指南 + CI 三层时间门控,读完能让安全测试跟功能测试在同一套 CI 里同跑,不抢镜、不漏检、不重复造轮子。
一、为什么测试工程师必须懂安全测试
工业里一个危险的分工:测试工程师管"功能对不对",安全团队管"安不安全" 。听起来分工清晰,实际会让安全漏洞从评审 → 编码 → CI → 预发一路漏到生产,等到安全团队扫出时已经累积成"债"。
具体撕掉两个常见误区:
| 误区 | 真相 |
|---|---|
| "安全是安全团队的事" | 90% 的安全漏洞发生在功能代码里(SQL 注入、XSS、PII 泄露),测试工程师写测试时眼睛一瞥就能拦下 |
| "安全测试要懂渗透才行" | DAST / SAST / IAST 三类都是工具自动扫,测试工程师的活是"接到 CI 流水线 + 看懂报告 + 红线拦截" |
回扣 Day 17 §2.6 安全维度 + Day 19 协作地盘------测试工程师已经在 PR review 里看过 SQL 注入了 。Day 24 是把那条红线进化为系统化的工具链。
二、DAST / SAST / IAST 三种本质区分
这三种安全测试常见被混着用。先用一张表对清楚:
| 维度 | SAST | DAST | IAST |
|---|---|---|---|
| 全称 | Static Application Security Testing | Dynamic Application Security Testing | Interactive AST |
| 中文名 | 静态安全测试 | 动态安全测试 | 交互式(运行时) |
| 输入 | 源代码 / bytecode | 运行中的 HTTP endpoint | 跑测试时的运行时插桩 |
| 跑在哪一阶段 | 编码期 / PR | 集成期 / 预发 | 集成测试期 |
| 能发现 | 代码模式层漏洞(SQL 注入源码) | 行为层漏洞(实际能 payload 攻击) | 代码 + 行为 + 数据流 |
| 误报率 | 高(20%-40%) | 中(5%-10%) | 低(<5%) |
| 速度 | 快(秒级) | 慢(分钟到小时级) | 跟功能测试同速 |
| 代表工具 | SonarQube / Semgrep / CodeQL | OWASP ZAP / Burp Suite / Nuclei | Contrast Security / Seeker |
读完这张表的关键结论 :三种不是替代关系,是互补关系。它们在不同阶段守不同红线。
2.1 三种测试的速度-有效性曲线
有效性 │ DAST ●
│ ●
│ ● ← 找得到真漏洞,但慢
│ ●
│ ● IAST ★
│ ● ← 平衡点(本篇推荐)
│ ●
│ SAST ● ← 快,但误报多
│●─────────────────────── 速度
秒级 分钟级 小时级
IAST 在曲线中段------速度跟集成测试同速(因为它就是在集成测试里插桩),有效性接近 DAST。这就是为什么工业里 IAST 是"性价比之王"------但门槛也最高(要装 agent,要业务配合)。
2.2 三种测试本质对应三种「认知层」
换个角度看 --- DAST / SAST / IAST 各自看到的安全世界分别对应认知层级:
- SAST 看代码 :就像静态类型检查(TypeScript / Java 类型系统),
能拦住「明显会出错」的写法,但拦不住「运行时拼出来的恶意输入」; - DAST 看行为 :就像功能测试,从外部接口真实发起攻击,
能发现「代码看着没毛病但行为上能被攻破」的漏洞; - IAST 看数据流 :就像 debug 时的断点,
它盯着「恶意 payload 从 HTTP 进入到 JDBC 出去的整个数据路径」,
看得到 SAST 看不到的「拼接发生在运行时」、
也能修掉 DAST 发现晚的「要发请求才知道」。
这三种认知层的差异,意味着任何一种单独都不够 。
SAST 看不到运行时拼接、DAST 看不到代码上下文、
IAST 看不到「跑测之外的代码路径」。
工业实践里只有「三选一」的团队,
都会出现「同一类型漏洞反复发生」的反复事故。
这一观察也对应到 Day 1 测试金字塔的精神:
金字塔不是「单测就够了」,是「每一层都不可少」 ,
安全测试同理。
三、SAST:静态扫描 + PR 红线
最便宜的一档,所有团队都该接。
3.1 Semgrep 案例:SQL 注入红线
yaml
# .semgrep.yml
rules:
- id: java-sql-injection-string-concat
patterns:
- pattern: |
String $SQL = "...$VAR...";
$STMT.executeQuery($SQL);
- pattern-not: |
$STMT = $CONN.prepareStatement(...);
message: "可能 SQL 注入 --- 字符串拼接构造 SQL,建议改 PreparedStatement"
languages: [java]
severity: ERROR
metadata:
cwe: "CWE-89: SQL Injection"
- id: pii-logging-mobile
pattern-regex: 'log\w*\([^)]*\b(user|mobile).*(get)?Mobile\([^)]*\)'
message: "可能 PII 泄露 --- 日志直接打了手机号"
severity: WARNING
metadata:
cwe: "CWE-532: Insertion of Sensitive Info into Log"
接到 PR check:
yaml
# .github/workflows/sast.yml
- name: Semgrep SAST
uses: returntocorp/semgrep-action@v1
with:
config: .semgrep.yml
env:
SEMGREP_RULES: .semgrep.yml
关键设计:
- SAST 只接 PR,不接每夜------快速反馈,5 秒级;
- 误报一定要 tune(用
pattern-not排除合理用法),否则开发会装作没看见; - severity ERROR 必须红灯拦 PR,WARNING 进 review 评论。
3.2 SonarQube vs Semgrep
我推荐主力用 Semgrep + SonarQube 补 Code Quality:
| 工具 | 强项 | 适合场景 |
|---|---|---|
| Semgrep | 规则可写、CI 友好 | 自定义漏洞红线(业务规则 + 安全) |
| SonarQube | 长期质量度量、复杂度 | 综合 Code Health,但不擅长安全定制 |
| CodeQL(GitHub) | 跨过程分析能力强,但慢 | 每夜深度扫,不进 PR |
回扣 Day 7 --- 时间门控同样适用于安全测试:SAST 进 PR,DAST 进每夜。
3.3 SAST 误报 tune 的工程动作(实战)
接好 SAST 不等于「上线就高枕无忧」------刚开始接的 1-2 个月,
误报可能占 30-50%,如果不去 tune,
SAST 红灯会变成「开发都装看不见」的噪声源。
三个标准 tune 动作:
动作一:跑一周后,把所有 ERROR 级别告警分类:
python
# tools/sast-triage.py
import json
from collections import Counter
def triage_sast_rules(report_path):
report = json.load(open(report_path))
rule_freq = Counter(f['rule_id'] for f in report['findings'])
return {
'frequent_rules': rule_freq.most_common(10),
'recommendation': 'top 3 规则误报 > 50% 必 tune, 0 命中规则该 review',
}
动作二:误报的根因有 4 种,分别用不同方式 tune:
| 误报根因 | 表现 | tune 方式 |
|---|---|---|
| 业务库 API 被误判 | safeMapper.query() 被报注入 |
加 pattern-not 排除业务库名 |
| 已用 PreparedStatement 但识别错 | 看到 + 拼接就报 |
用 metavariable-pattern 精确匹配 |
| 测试 fixture 被扫出 | password' OR 1=1 被报 |
.semgrep.yml 排除 src/test/** |
| 框架内置 SQL builder 被扫 | MyBatis Stream API 误报 | 配置预设 library 排除 |
动作三 :每月一次「假阳复盘」。top 5 假阳规则进 issue tracker,
分配 owner 限 30 天 tune ------ 不能让假阳永远挂在那儿。
回扣 Day 20 §10 指标反模式 ------ SAST 工具一旦不 tune,
1 年后会成为「摆设」 ,红灯被开发屏蔽,工具还在跑但保护力归零。
这是工程上常见的「安全债」坑:工具上线了,后续投入没跟上,
渐渐就流于形式。
四、DAST:动态扫描 + 周期性深度
DAST 跟 SAST 互补 --- 它不知道代码长什么样,只用 payload 攻击运行中的服务。
4.1 OWASP ZAP 接到 CI
python
# tools/dast-scan.py
import subprocess, json, sys
def zap_scan(target_url: str, intensity: str = "normal"):
"""
对 target_url 跑 OWASP ZAP 扫描;
intensity 分档:quick(2min)/ normal(15min)/ deep(60min)
"""
result = subprocess.run([
"zap-cli", "quick-scan",
"--self-contained",
"-t", target_url,
"-i", intensity,
"-f", "json",
"-o", "zap-report.json",
], capture_output=True)
report = json.load(open("zap-report.json"))
high_risk = [alert for alert in report["alerts"]
if alert["riskcode"] in ("3", "2")] # High + Medium
if high_risk:
print(f"❌ DAST found {len(high_risk)} high/medium-risk issues")
for a in high_risk:
print(f" - [{a['riskcode']}] {a['name']}: {a['url']}")
sys.exit(1)
print(f"✅ DAST clean ({len(report['alerts'])} low/info)")
zap_scan(target_url="https://staging.internal", intensity="normal")
接进每夜:
yaml
# .github/workflows/dast-nightly.yml
name: Nightly DAST Scan
on:
schedule: [{cron: "0 3 * * *"}] # 每夜 3 点
workflow_dispatch:
jobs:
dast:
runs-on: security-runner
steps:
- name: Run OWASP ZAP
run: python tools/dast-scan.py --intensity deep
DAST 不进 PR,只进每夜 + 发版前------慢、要起完整 staging。
4.2 DAST 的两个坑
坑一 :扫描需要认证态。如果 staging 有登录,DAST 扫不到登录后的接口,主要功能区域漏掉。解法:在 ZAP 配置里加 session token(预先登录拿 cookie):
python
zap_cli_script = """
var sess = session.open("oauth");
sess.set("Cookie", "SESSION=...");
session.includeInContext("all", "https://staging.internal/.*");
"""
# 跑前先 load session
坑二 :写操作 DAST 会乱搞(POST /orders 真的会创建假单)。解法 :DAST 只对 staging 跑,且 staging 必须有"测试数据隔离"(回扣 Day 14 §6 数据治理红线)。
4.3 DAST 报告要进度量(Day 20 接入)
DAST 跑出来的告警不能只丢 issue tracker,要进 OKR 度量体系(回扣 Day 20 §6 测试资产):
yaml
# dashboards/security-quarterly.q
panels:
- title: 'DAST findings by severity(过去 90 天)'
query: |
SELECT
severity,
COUNT(DISTINCT finding_id) AS findings,
AVG(days_to_fix) AS avg_sla_days
FROM dast_findings
WHERE detected_at > now() - interval '90 days'
GROUP BY severity
三个对应度量:
- DAST_findings_high_unfixed(P0 漏洞未修数);
- DAST_mean_time_to_fix(漏洞平均修复时长);
- DAST_recurring_rate (同类漏洞复发率 ------ 这条最有信号,
高 = 团队没学到根因)。
对比 Day 20 §3 的测试质量指标体系,
安全维度的「recurrence rate」等价于「escape rate」------
都是漏检指标的镜像。
五、IAST:运行时插桩 --- 性价比之王
IAST 的精髓:在跑功能测试时,顺手做安全检查,不用额外扫一次。
5.1 原理图
┌────────────────────────────────────────────┐
│ 功能测试跑(OrderCreateTest 等) │
│ ↓ 调用 │
│ 被测应用(已装 IAST agent) │
│ ├→ agent 拦 JDBC: 看到 "; DROP TABLE" │
│ │ → 标记 "可能 SQL 注入" │
│ ├→ agent 拦 HTTP out: 看到 SSN 字段 │
│ │ → 标记 "可能 PII 外发" │
│ └→ 上报到 IAST 控制台 │
└────────────────────────────────────────────┘
也就是说,功能测试覆盖到的代码路径,IAST 都会自动扫 ,且误报率 < 5%(因为它看到的是真数据流,不是静态推断)。
5.2 一个伪部署
yaml
# docker-compose.iast.yml
services:
app:
image: order-service:iast
environment:
- JAVA_TOOL_OPTIONS=-javaagent:/agents/contrast-agent.jar
- CONTRAST__API__URL=https://app.contrastsecurity.com
- CONTRAST__API__SERVICE_KEY=${IAST_KEY}
- CONTRAST__SERVER__NAME=staging-iast
test-runner:
image: test-runner:latest
command: mvn test -Dspring.profiles.active=iast
注意整个集成测试套件就是 IAST 的"扫描面" ------测试覆盖越广,IAST 越有效(回扣 Day 20 §6 测试资产指标)。这是 Day 24 跟 Day 20 的关键咬合点。
5.3 IAST 的两个坑
坑一 :性能开销。IAST agent 让单测速度降到 60-70%。解法 :只对集成测试插桩,不对单测(单测本就跑得快,加 IAST 浪费);Spring profile 隔离。
坑二 :agent 装错版本。Java 升级到 21,旧 contrast-agent 没跟,跑出来零报。解法:agent 版本号进 OKR 度量(回扣 Day 20 §10 "指标反模式"------监控工具有效性)。
5.4 IAST 跟契约测试(Day 9)的天然咬合
IAST 的覆盖面跟功能测试同源 ------
功能测覆盖到的 endpoint,IAST 就会扫 ;
功能测覆盖不到的,IAST 也看不到。
这跟 Day 9 Pact 契约测试的边界完全一致:
契约保证跨服务协议,IAST 检查同一协议的安全。
落地动作:把 IAST 控制台的报告按 Pact 契约对齐:
python
# tools/cross_check_pact_iast.py
def cross_check(pacts, iast_findings):
# 每个 Pact 对应一条 IAST 报告
paired = []
for pact in pacts:
matching = [f for f in iast_findings if f.endpoint == pact.endpoint]
paired.append({
'pact_verified': pact.verified,
'pact_endpoint': pact.endpoint,
'iast_findings': len(matching),
'iast_high': sum(1 for m in matching if m.severity == 'high'),
'cross_coverage': bool(pact.verified and matching),
})
return paired
两份报告交叉看 会发现一类真相:
Pact 通过但 IAST 报危险 → 协议对但代码层有漏洞;
Pact 红但 IAST 没报 → 协议变了但新代码也没扫到(白测)。
这是 Day 24 在「测试 + 安全」融合块的最有价值产出:
测试工程师借 IAST 的报告回看契约测试,反向提质量。
六、把三种接进 CI 全流程
把 Day 24 全部汇总到一张实施图:
提交 PR
↓
┌────────────────────────────┐
│ PR Check(≤ 5 min) │
│ - SAST (Semgrep) │ ← 静态扫描
│ - 单测 │
│ - 契约 verify │
└────────────────────────────┘
↓ merge
┌────────────────────────────┐
│ 集成测试(每夜,IT runner) │
│ - 集成测试 + IAST agent │ ← IAST 插桩,功能+安全同跑
└────────────────────────────┘
↓
┌────────────────────────────┐
│ 每夜 + 发版前 │
│ - DAST (ZAP) 对 staging │ ← 行为层深度扫
└────────────────────────────┘
↓
生产
对应 Day 7 CI 时间门控 + Day 23 Honeycomb 决策树:
| 阶段 | 安全测试 | 速度 | 同跑的功能测试 |
|---|---|---|---|
| PR | SAST | 秒级 | 单测 + 契约 |
| 集成 | IAST | 与功能测同速 | Honeycomb 集成层 |
| 发版前 | DAST | 慢(但可以接受的 30 min) | E2E 冒烟 |
每一档都"跟功能测试同跑",不抢镜。
七、回扣系列
| Day | 在 Day 24 它接到的线 |
|---|---|
| Day 1 测试金字塔 | §6 SAST 跟单测同档 |
| Day 4 Testcontainers | §5 IAST agent 包装在容器里 |
| Day 5 WireMock | §4 DAST 配合 WireMock 隔离外部依赖 |
| Day 7 CI 分层 | §6 SAST 进 PR,DAST 进每夜 |
| Day 9 契约测试 | 契约层天然有 IAST 插桩 |
| Day 10 flaky 治理 | DAST 周期性 brief 拿不到认证会 flaky |
| Day 14 数据治理 | DAST 写操作必隔离 |
| Day 17 PR review | §3 SAST 红线 = PR review 自动化 |
| Day 19 三方协作 | DevOps 提供 staging,SRE 监控 IAST,测试主导 |
| Day 20 OKR 度量 | §5.3 agent 版本进度量(避免"工具失活") |
| Day 22 UI 自愈 | DAST 跨 UI 流也能跑 |
| Day 23 Honeycomb | §6 SAST/IAST/DAST 全部对应金字塔四层 |
12 条对应。Day 24 是「测试 + 安全」第一次正面对接,跟 Day 21/22/23 三联篇形成完整「测试 + 非功能」补全。
八、反模式清单
| 反模式 | 后果 | 干掉的办法 |
|---|---|---|
| SAST 误报多但没人 tune | 开发忽视,红灯被屏蔽 | §3.1 每月 review 假阳,加 pattern-not |
| DAST 在 PR 上跑 | CI 时间从 5 分钟炸到 1 小时 | §6 DAST 只在每夜 |
| IAST agent 装到生产 | 性能砸了,生产出事故 | §5 IAST 只装 staging |
| 漏洞进了 ADR(漏洞登记册)但不修 | 安全债越积越深 | 漏洞必须有 SLA(高 7d / 中 30d / 低 90d) |
| 只用 SAST 误以为是"安全了" | 行为层漏洞(SAST 看不到)漏掉 | 三种都接,各有边界 |
| DAST 扫 staging 时没数据 | 多数 endpoint 不暴露,扫不出 | staging 装 synthetic 数据(回扣 Day 14) |
| IAST 接了但单测用 | 报告空白 + 性能砸 | §5.3 用 Spring profile 隔离到集成测试 |
九、原则三句话
- SAST / DAST / IAST 互补,不可单选 ------ 三档各守一条线,缺一档都会暴露对应类型漏洞;
- IAST 是性价比之王(有效性高、误报少),但门槛高,适合中大型团队;
- 跟功能测试同行 ------ SAST 在 PR,IAST 在集成测试,DAST 在每夜,不抢功能测试的镜。
9.1 一句工程口诀(可贴墙上)
读过本篇后,团队 review PR 时可以套用:
「PR 静扫、跑测插桩、夜放攻击」 ------
SAST 进 PR 守代码层,IAST 跟功能测插桩看数据流,
DAST 在每夜对 staging 发起真攻击。
三层各管一段,谁都替代不了谁。
十、思考题
留给今晚:
- 你团队接了哪几档(SAST / DAST / IAST 全 / 部分)?最常见的盲点是哪一档?
- 上次安全漏洞在 PR / 集成 / 发版 / 生产哪一层发现的?哪一档该提前拦截?
- IAST 误报 < 5%,你的功能测试覆盖率如果只有 50%,IAST 的有效性会受多大影响?(慢思考)
- 你的 CI 时间里,有多少 % 是安全测试?如果 > 30%,该考虑 §6 的分层是否合理。
十一、TL;DR
- 三种测试本质区分:SAST 看代码,DAST 攻行为,IAST 看数据流;
- 各自落地档:SAST 进 PR(秒级)、IAST 进集成测试(同速)、DAST 进每夜(分钟到小时);
- IAST 是性价比之王 ,但 agent 维护成本高,适合中大型团队;
- 三种都必须接 ,不接是漏,全接是负担,分级是工程取舍;
- 12 条对位系列前文,Day 24 完成「测试 + 安全」第一次正面咬合。
下一篇:Day 25 · 性能压测的容量规划:把"找拐点"上升为"找容量曲线方程"
Day 15 给过"拐点 × 0.7 当生产安全水位"的速写,Day 25 把它推到工程化 ------ 在多个并发档跑出来的散点上
拟合容量曲线方程、给容量数据库建模、用方程外推预测"加 5 个 pod 能扛多少 QPS"。下篇见 👋