Shai-Hulud 蠕虫是 2025 年 NPM 供应链攻击的典型代表,通过 postinstall 钩子在依赖安装时执行恶意 bundle.js 脚本,利用 TruffleHog 扫描环境中的 NPM_TOKEN、GitHub PAT、AWS 凭证等敏感数据,并创建名为“Shai-Hulud”的公共 GitHub 仓库进行双 Base64 编码外泄,同时植入恶意 GitHub Actions 工作流实现持久化与自传播。该攻击已感染 187+ NPM 包,包括 CrowdStrike 管理的多个包,特别是在 CI/CD 环境中高权限 Secrets 易被窃取,导致连锁感染维护者其他包。
传统防范如 npm ci --ignore-scripts 可禁用 postinstall,但会破坏依赖完整性(如 native 模块构建)。工程化解决方案是容器化 NPM install,并应用 seccomp-BPF 过滤器限制 postinstall 脚本 syscall,允许必要文件读写(安装依赖),但阻塞网络(connect/sendto)、进程创建(execve/fork)、敏感目录访问(.aws/.ssh)。Seccomp 是 Linux 内核功能,支持在容器运行时(如 Docker/Podman)加载 JSON 过滤器,实现细粒度 syscall 白名单,仅对 x86_64 架构生效,默认拒绝未列 syscall(SCMP_ACT_ERRNO)。
核心实现步骤
-
生成 syscall 基线过滤器
使用 podman oci-seccomp-bpf-hook(Red Hat 工具)追踪干净 NPM install 的 syscall:
sudo podman run --annotation io.containers.trace-syscall=of:/tmp/npm-ci.json node:20-alpine sh -c "npm ci --production && echo done"
输出 JSON 示例(精简后约 50 个 syscall):
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{"names": ["access", "arch_prctl", "brk", "close", "fstat", "futex", "getdents64", "getpid", "getrandom", "ioctl", "lseek", "mmap", "mprotect", "munmap", "openat", "pread64", "prlimit64", "read", "recvmsg", "renameat2", "rt_sigaction", "sendmsg", "statx", "write"], "args": []},
{"names": ["clone"], "args": [...]},
]
}
迭代测试:对污染包运行,验证阻塞 exfil(e.g., curl/webhook 失败,syscall 杀进程 SCMP_ACT_KILL)。
-
Dockerfile 容器化模板
FROM node:20-alpine AS npm-install
WORKDIR /app
# 挂载 seccomp 配置文件
COPY seccomp-npm.json /etc/docker/seccomp-npm.json
COPY package*.json ./
# 生产安装,禁用 devDep 以减 syscall
RUN --security-opt seccomp=/etc/docker/seccomp-npm.json \
npm ci --only=production --no-audit --no-fund --network-timeout=300000
# 提取 node_modules
RUN tar czf /node_modules.tar.gz node_modules package-lock.json
FROM scratch AS export
COPY --from=npm-install /node_modules.tar.gz /
关键参数:
| 参数 |
值 |
目的 |
| --network-timeout |
300000ms |
防挂起 exfil |
| --maxsockets |
10 |
限并发网络 |
| --production |
true |
减 syscall 面 |
| ulimit -n |
1024 |
限文件句柄防 fork 炸 |
| --memory |
512MiB |
防 OOM 逃逸 |
-
GitHub Actions CI/CD 集成
jobs:
install:
runs-on: ubuntu-22.04
container:
image: your-npm-sandbox:latest # 预建镜像含 seccomp
security-opt:
- seccomp:/path/seccomp-npm.json
steps:
- uses: actions/checkout@v4
- run: docker run --rm -v ${{ github.workspace }}:/app -v /tmp:/host-tmp \
--security-opt seccomp=seccomp-npm.json \
--ulimit nofile=1024:1024 --memory=512m \
your-npm-install-image tar xzf /node_modules.tar.gz -C /app
- run: npm run build # post-install 已沙箱
回滚策略:若 install 失败(syscall 违规),fallback 到 --ignore-scripts 并告警。
监控与阈值参数
- syscall 违规指标:Docker daemon 日志 grep "seccomp" | jc --seccomp,Prometheus exporter 采集 denied_calls >0 告警。
- 超时阈值:postinstall >120s 杀掉(--max-time 120)。
- 文件访问白名单:chroot /tmp/app,仅读 package.json/node_modules,mknod 阻塞设备。
- 验证清单:
- 模拟 Shai-Hulud:npm i 污染包,检查无网络流量(tcpdump)。
- TruffleHog 扫描输出:仅 mock 数据,无真实凭证。
- GitHub repo 检查:无 Shai-Hulud 分支/工作流创建。
风险:过滤器过严导致合法 postinstall(如 gyp rebuild)失败,需白名单扩展(e.g., + execve for node)。适用于 Linux CI,非 Windows/Mac。HelixGuard 等平台可预侦测污染包,结合 socket.dev 扫描。
资料来源:
- HelixGuard.ai(NPM 恶意包情报)。
- Shai-Hulud 分析:Aikido Security、Socket.dev、KrebsOnSecurity(2025-09 事件)。
- Seccomp:podman oci-seccomp-bpf-hook、Docker docs。
(字数:1268)