Skip to content

元数据卡

  • 前置知识:第4章(Web 安全)、第5章(操作系统安全)、常规软件开发流程
  • 预计时间:50 分钟
  • 核心难度:进阶
  • 完成标志:能解释 SDL 的各个阶段,能用 STRIDE 模型分析一个系统的威胁,能区分 SAST、DAST、IAST 的适用场景

你的进度

你已经逐层构筑了魔法驿道的防线:加密咒语、符文证书体系、Web 应用护盾、操作法阵隔离、魔力管道监控。

但有一个问题没有解决:所有这些防御知识,每次都得在出现了漏洞之后才去学。上一个 SQL 注入修好了,下一个 XSS 又冒出来了。你需要的不是逐个漏洞打补丁,而是一个系统性的方法——在法力咒语还没写的时候就想清楚安全风险。

安全不是运维加一个 WAF 就行,安全是一个工程流程问题。 你的任务

理解安全开发生命周期(SDL)——如何把安全嵌入到软件开发的每个阶段而不是留到最后。你将掌握威胁建模(STRIDE)、安全测试(SAST/DAST/IAST)、依赖管理和安全响应计划。

本章分层

  • 必读:SDL 七阶段、STRIDE 威胁建模(含实例)、SAST vs DAST
  • 选读:SCA(软件组成分析)、DeviSecOps 实践
  • 进阶:OWASP ASVS 标准、Supply Chain Levels for Software Artifacts (SLSA)

破局 · 溯源

问题:你回顾这段时间的经历——每一个漏洞几乎都是事后发现的。SQL 注入是上线后扫出来的,XSS 是用户报告说"你们的页面弹奇怪东西"才发现的,SSH 端口暴露在外网是渗透测试发现的。

问题不在于"没有发现漏洞",而在于"发现得太晚"。越晚发现安全漏洞,修复成本越高:

需求阶段发现:修复成本 1x
设计阶段发现:修复成本 6x
实现阶段发现:修复成本 15x
测试阶段发现:修复成本 30x
运维阶段发现:修复成本 100x+

核心教训:安全不能是最后一道工序。它必须是嵌入到每个阶段的需求。

第一块:SDL(安全开发生命周期)

SDL 是微软在 2000 年代初提出的方法论。它的核心是把安全活动加入到软件开发的标准流程中:

需求阶段 ─→ 设计阶段 ─→ 实现阶段 ─→ 验证阶段 ─→ 发布阶段 ─→ 响应阶段
   │            │           │           │           │           │
   ├─安全需求    ├─威胁建模   ├─SAST     ├─DAST      ├─安全审查   ├─漏洞应急
   ├─安全培训    ├─安全设计   ├─依赖检查  ├─渗透测试   ├─配置基线   ├─热修复
                 ├─攻击面分析 ├─安全编码  ├─合规检查   │
                                                  └─签名 + SBOM

需求阶段:

  • 确定安全需求:系统需要满足哪些安全标准?
  • 数据分类:系统处理什么类型的数据?PII(个人身份信息)?信用卡(PCI)?
  • 安全培训:开发团队有没有足够的安全知识?

设计阶段——STRIDE 威胁建模:

STRIDE 是由微软提出的一种威胁分类模型,让你在系统设计时就系统性地找出安全问题。

类别威胁被破坏的安全属性示例
Spoofing假冒身份认证伪造 JWT 签名
Tampering篡改数据完整性修改 HTTP 请求体
Repudiation否认操作不可抵赖性用户否认发起了某个操作
Information Disclosure信息泄露机密性日志中输出密码
Denial of Service拒绝服务可用性频繁请求压垮 API
Elevation of Privilege权限提升授权普通用户执行管理员操作

用 STRIDE 分析"巡逻报告系统":

系统描述:一个 REST API,前端展示巡逻报告,后端连接 PostgreSQL 数据库

数据流图(简化):

用户 ─→【前端】──HTTP──→【API 服务】──SQL──→【数据库】

                                    └──→【日志服务】

STRIDE 分析每个组件:

【前端】
  S: 攻击者伪造其他用户的 session
  T: 修改前端 JS 逻辑
  R: 用户否认提交了内容(需要访问日志记录)
  I: 在 URL 中暴露 session ID
  D: 大量并发请求导致前端不可用
  E: 通过 XSS 提升到同站其他用户的权限

【API 服务】
  S: 伪造 JWT 签名(如果使用弱算法 "none")
  T: 通过 SQL 注入篡改数据库
  R: 没有请求日志,用户否认操作
  I: 错误信息暴露堆栈跟踪和表结构
  D: 高并发 SQL 查询耗尽数据库连接池
  E: API 端缺少权限检查,越权查看他人报告

【数据库】
  S: 允许无密码的本地 socket 连接
  T: 用弱权限用户连接,可修改系统数据
  I: 存储了明文密码
  D: 连接数未限制,被耗尽
  E: 数据库用户有 DROP TABLE 权限

【日志服务】
  S: 伪造日志条目
  T: 日志被外部写入(日志注入)
  R: 日志被删除,无法追溯
  I: 日志包含敏感数据(token/密码)
  D: 日志文件撑爆磁盘

完成 STRIDE 分析后,为每个找到的威胁分配优先级和应对措施:

威胁严重性应对措施
SQL 注入缓解参数化查询
越权访问缓解增加权限检查
JWT 弱算法缓解强制 RS256,校验算法白名单
日志包含 token缓解日志过滤器自动脱敏
明文密码存储缓解用 Argon2 哈希
数据库 DROP 权限缓解最小权限用户

第二块:安全测试工具

SAST(Static Application Security Testing)

SAST 在代码层面做静态分析,不需要运行程序。像 spellcheck 但检查的是安全漏洞。

bash
# SonarQube(社区版支持 Java/C#/Python/JS 等)
sonar-scanner \
  -Dsonar.projectKey=patrol-report \
  -Dsonar.sources=. \
  -Dsonar.host.url=http://localhost:9000

# Bandit(Python 专用)
pip install bandit
bandit -r src/ -f json -o bandit-report.json

# SpotBugs + FindSecBugs(Java)
mvn compile
mvn com.github.spotbugs:spotbugs-maven-plugin:spotbugs

SAST 能检测到的问题:

  • 硬编码的密码和密钥
  • SQL 注入(检测字符串拼接)
  • 不安全的随机数使用
  • 危险函数调用(eval()exec()
  • 过期的依赖库

DAST(Dynamic Application Security Testing)

DAST 从外部测试正在运行的应用。它模拟攻击者的视角——不知道源码,通过发送恶意请求来探测漏洞。

bash
# OWASP ZAP(最流行的开源 DAST)
docker run -u zap -p 8080:8080 -i ghcr.io/zaproxy/zaproxy \
  zap.sh -port 8080 -host 0.0.0.0

# 被动扫描(通过代理监听流量)
# 主动扫描
docker run -v $(pwd):/zap/wrk/:rw -t ghcr.io/zaproxy/zaproxy \
  zap-cli quick-scan --self-contained \
  --start-options '-config api.disablekey=true' \
  http://localhost:8081/supply-app/

DAST 能检测到的问题:

  • XSS(反射型和存储型)
  • CSRF
  • 不安全的 HTTP 头
  • SQL 注入(通过请求触发)
  • 目录遍历
  • Open redirect

SAST vs DAST:

维度SASTDAST
需要源码
需要运行应用
发现阶段实现阶段测试阶段
误报率较高(需要人工确认)较低(触发式验证)
覆盖范围所有代码路径运行时可达的路径
配置复杂度中(需要应用运行环境)
典型工具SonarQube, Bandit, CheckmarxOWASP ZAP, Burp Suite, Acunetix

IAST(Interactive Application Security Testing,交互式应用安全测试):

结合 SAST 和 DAST 的优点——在运行的应用中植入探针,在 DAST 扫描的从内部监控代码执行路径。

DAST 从外部发送测试请求


应用运行中 ──→ IAST 探针捕获代码执行路径
            │                │
            ▼                ▼
      返回结果         分析实际执行的代码路径
                        标记有漏洞的代码

IAST 的优势:它知道哪些代码路径被实际执行了,所以比 SAST 少很多误报。它也能检测到 DAST 无法覆盖的深层逻辑(如 DAST 无法看到的业务逻辑漏洞)。

第三块:依赖管理和供应链安全

现代应用 90% 以上的代码来自第三方依赖。一个被攻破的依赖等于给你的要塞开了一扇后门。

SCA(Software Composition Analysis):

bash
# OWASP Dependency-Check
dependency-check --scan target/ --format HTML

# npm audit(JavaScript 依赖检查)
npm audit --json

# Trivy(支持多种语言和容器镜像)
trivy fs src/
trivy image my-app:latest

SCA 的核心是检查已知漏洞数据库(CVE / NVD)——如果你的依赖库有一个 CVE-2023-xxxxx,SCA 会在 CI 中阻止发布。

SBOM(软件物料清单,Software Bill of Materials):

SBOM 是一个清单,列出你的软件中所有组件和依赖。在供应链攻击(如 SolarWinds 事件)后,SBOM 已经是关键基础设施的基本要求。

json
{
  "$schema": "https://raw.githubusercontent.com/CycloneDX/specification/master/schema/bom-1.4.schema.json",
  "bomFormat": "CycloneDX",
  "specVersion": "1.4",
  "components": [
    {
      "type": "library",
      "name": "fastapi",
      "version": "0.104.0",
      "purl": "pkg:pypi/fastapi@0.104.0",
      "licenses": [{"license": {"id": "MIT"}}],
      "hashes": [{"alg": "SHA-256", "content": "..."}]
    }
  ]
}

生成 SBOM:

bash
# Python 项目
pip install cyclonedx-bom
cyclonedx-py requirements.txt --output bom.json

# Node.js 项目
npm install -g @cyclonedx/cyclonedx-npm
cyclonedx-npm --output-file bom.json

第四块:漏洞响应

即使做了所有预防,漏洞仍然会发生。关键是发生后的响应速度。

发现漏洞

   ├── 评估严重性(CVSS 评分)
   │    CVSS 3.1:AccessVector + AttackComplexity + PrivilegesRequired + UserInteraction
   │    → Critical (9.0-10.0) / High (7.0-8.9) / Medium (4.0-6.9) / Low (0.1-3.9)

   ├── 制定修复方案
   │    Critical:24小时内修复上线
   │    High:1周内
   │    Medium:在下一个发布周期修复

   ├── 应用补丁 / 热修复

   ├── 发布安全通告(如果影响用户)

   └── 事后复盘(Root Cause Analysis)
        └── 更新 SDL 流程,防止同类漏洞再次出现

用 Python 快速获取已知漏洞信息:

python
import requests

# NVD API(National Vulnerability Database)
def get_cve_details(cve_id):
    url = f"https://services.nvd.nist.gov/rest/json/cves/2.0?cveId={cve_id}"
    resp = requests.get(url)
    data = resp.json()
    
    cve = data['vulnerabilities'][0]['cve']
    metrics = cve.get('metrics', {})
    
    # 取 CVSS 3.1 评分
    cvss_v3 = metrics.get('cvssMetricV31', [{}])[0].get('cvssData', {})
    
    return {
        'id': cve_id,
        'description': cve['descriptions'][0]['value'],
        'severity': cvss_v3.get('baseSeverity'),
        'score': cvss_v3.get('baseScore'),
        'vector': cvss_v3.get('vectorString'),
    }

print(get_cve_details("CVE-2021-44228"))  # Log4Shell

常见陷阱

  • 安全测试只在发布前做一次。 安全是持续的过程,不是节前大扫除。CI/CD 中集成 SAST/DAST,每次提交都检查。
  • 漏扫就是安全测试。 很多组织的"安全测试"只是跑一次漏洞扫描器。这和真正的安全工程相差甚远——威胁建模、渗透测试、代码审查、依赖性分析都要覆盖。
  • 认为云服务提供商负责安全。 云安全是共享责任模型(Shared Responsibility Model)。AWS/Azure/GCP 负责云基础设施的安全("云的安全"),你负责在云中部署的应用的安全("云中的安全")。
  • 依赖扫描只做一次。 依赖的漏洞是持续发现的。npm audittrivy 应该在每次 CI 构建中运行。
  • 没有安全回退计划。 当你发现一个严重的 0-day 漏洞,你的团队知道先做什么吗?有没有一个 playbook?关键人员有没有联系方式?——这些应该在安全响应计划中预先定义。
  • 忽略业务逻辑漏洞。 扫描器可以发现 SQL 注入和 XSS,但扫描器发现不了"用户可以给自己转账"或者"用户可以退货超过购买数量"这种业务逻辑缺陷。这些需要威胁建模、代码审查和渗透测试。

通关挑战

  • 热身:对一个你熟悉的内部系统,用 STRIDE 模型做一次完整的威胁建模。识别出至少 5 个潜在的威胁,并按严重性排列优先级。
  • 挑战:在 CI 管道中集成 SAST 和依赖检查。用 GitHub Actions 或 GitLab CI,加入 Bandit(Python)或 SonarQube,以及 Trivy 依赖扫描。配置成:严重漏洞(Critical/high)阻断合并(MR)。
  • 观察:运行 OWASP ZAP 对本地运行的 web 应用做一次被动扫描,阅读扫描报告,找到至少一个漏洞并确认修复。
  • 排障:你发现 SAST 扫描报告中有 100 个告警。你先处理了 10 个关键告警,但剩下的 90 个低风险告警占满了后续 Sprint。如何制定一个策略来逐步消除这些告警?(提示:可以考虑新代码零容忍 + 遗留债务分级处理)

旅人笔记

  • SDL 把安全嵌入到需求、设计、开发、测试、发布、响应每个阶段
  • 威胁建模(STRIDE)是设计阶段发现安全问题的系统化方法
  • SAST(静态分析)在代码阶段发现问题;DAST(动态测试)在运行时模拟攻击
  • IAST 结合两者优势,在实际执行路径上检测漏洞
  • SCA 和 SBOM 解决供应链安全——知道你的软件里有什么依赖
  • 漏洞响应计划必须在漏洞发生之前就准备好

下一站预告

你学完了如何保护系统。最后一个问题:系统里存储的用户数据怎么办?数据不是代码——它有法律人格,有合规要求。下一章,我们看隐私与数据保护——GDPR、数据匿名化、数据生命周期管理。

Built with VitePress | Software Systems Atlas