安全测试不是上线前的安检
很多团队把安全当成发布前的一道安检:功能都测完了,才让安全工程师拿扫描器扫一轮,扫出几个高危就修,扫不出来就放行。结果往往是高危漏洞反复出现,同一类问题换了个接口又来一遍。
真正的安全测试是一条贯穿开发全流程的防线:设计阶段做威胁建模,编码阶段跑静态扫描,联调阶段做动态测试,上线前再做一次汇总审计。越早发现问题,修复成本越低,这是安全界最朴素也最有效的规律。
在需求阶段修一个漏洞的成本,可能是上线后修它的几十分之一。
- 威胁建模设计阶段,找出哪里可能被攻击。
- SAST编码阶段,静态分析源码找隐患。
- DAST联调阶段,黑盒扫描运行中的系统。
- 审计上线前,汇总依赖、配置与边界检查。
先摸清攻击面:威胁建模
不知道哪里可能被攻击,扫描就是在盲扫。威胁建模做的事很简单:把系统拆开,标出每个入口、每份数据、每条信任边界,再问一句这里可能发生什么坏事。
常见思路是沿着数据流走一遍:用户从哪个入口进来、数据经过哪些组件、在哪个环节被存储和处理、哪些组件可信哪些不可信。重点盘这几个地方:对外暴露的接口、身份验证与授权逻辑、第三方回调、文件上传下载、数据导出。
# 一张简化的威胁模型记录
组件: 用户登录接口
入口: POST /api/login
信任边界: 公网 → 网关 → 认证服务 → 用户库
威胁: 暴力破解 | 会话劫持 | SQL 注入 | 响应体注入
缓解: 限流+验证码 | 短期令牌 | 参数化查询 | 输出编码
# 每条威胁都要落到一个可执行的缓解动作上
威胁建模的产出不是文档,而是一串可执行的测试用例:模型里识别出的每条威胁,都要对应一条验证它的安全用例。
SAST:在代码里找隐患
静态应用安全测试(SAST)不运行程序,直接分析源代码和依赖,擅长抓一类问题:危险函数的使用、注入点的构造、密码学误用、不安全的反序列化。它的价值在于能进 CI,每次提交都扫一遍,把问题拦在合并之前。
# 伪代码:一条典型的不安全 SQL 拼接
query = "SELECT * FROM users WHERE name = '" + userInput + "'"
# 扫描器会标记:未参数化的 SQL 拼接,存在注入风险
# 修复:使用参数化查询
query = "SELECT * FROM users WHERE name = ?"
execute(preparedStatement, [userInput])
用 SAST 有两点要注意:一是误报管理,扫描结果要有人工确认和标注,否则告警太多就会被无视;二是规则要与技术栈匹配,Java 的注入点、前端脚本的 XSS 点、数据层的脱敏要求各不相同。
DAST:在黑盒里试漏洞
动态应用安全测试(DAST)把系统跑起来,从外部像攻击者一样发送请求,观察响应:测参数注入、测越权、测文件上传、测认证绕过。它不关心代码怎么写的,只关心系统实际暴露出来的行为。
| 维度 | SAST | DAST |
|---|---|---|
| 视角 | 白盒,看源码 | 黑盒,看行为 |
| 时机 | 编码阶段,每次提交 | 联调阶段,系统可运行后 |
| 擅长的 | 注入、密码学、代码坏味道 | 越权、认证、配置、运行时问题 |
| 短板 | 看不懂运行时的真实状态 | 扫不到未暴露的代码路径 |
两者互补而不是替代:SAST 保证「写出来的代码没埋雷」,DAST 验证「跑起来的系统真的安全」。
漏洞生命周期:从发现到闭环
扫描器会报一堆问题,治理的关键在闭环:每条漏洞都要有责任人、有优先级、有修复、有复测,最后有记录。漏洞不是测出来就完了,测出来没人管等于没测。
| 严重度 | 典型场景 | 响应时限 | 修复方式 |
|---|---|---|---|
| 严重 | 可未授权访问、可直接拿数据 | 立即修复 | 紧急版本或回滚 |
| 高 | 需一定条件才能利用的越权 | 24 小时内 | 热修复并补回归用例 |
| 中 | 信息泄露、加固不足 | 随迭代修复 | 纳入下一版本 |
| 低 | 代码规范类问题 | 持续跟进 | 列入技术债台账 |
修复完成不等于结束,还要复测验证:用同样的探测方式重新扫一遍,确认漏洞真的消失,并把这条用例固化进回归集,防止它换个写法又回来。
依赖与配置里的隐性风险
自己写的代码往往没多少漏洞,真正的高危多数来自第三方依赖:一个很久没升级的开源库,一个带默认口令的中间件,一个把密钥写在代码里的配置。这类问题靠源码扫描很难发现,要靠依赖审计和配置基线检查。
- 依赖审计:定期对照漏洞库,把有已知漏洞的依赖升级或替换。
- 配置基线:检查默认口令、调试开关、过宽权限、明文密钥。
- 密钥管理:密码、令牌、证书一律进密钥库,禁止硬编码。
- 最小暴露:不用的端口不开、不用的服务不装、多余的管理接口关掉。
高危漏洞从提交到修复的中位时间,往往以周甚至月计;而这期间系统一直暴露在风险里。
把安全测试做进流水线
安全测试只有自动化才能持续。推荐的流水线编排是:提交时跑 SAST 与依赖审计,构建时跑密钥与配置扫描,测试环境部署后跑 DAST,上线前做一次人工兜底审查。
# 流水线里的安全阶段(示意)
stage("sast") { scan_source_code() }
stage("dep-audit") { scan_dependencies() }
stage("secret-scan"){ scan_for_hardcoded_keys() }
stage("dast") { scan_running_env(staging) }
# 任一阶段发现高危,流水线直接中断,不让问题流向下一环
别指望一轮扫完所有问题。更现实的目标是让高危漏洞在发布前被拦下、让同类漏洞不再反复出现——把每一次修复沉淀成规则和用例,安全水平才会随迭代持续上升。