Hotdry.
ai-security

使用 Seccomp 沙箱容器化 NPM 安装:防御 Shai-Hulud postinstall 凭证窃取

针对 Shai-Hulud 蠕虫 postinstall 钩子凭证扫描与 GitHub Actions 外泄,提供 seccomp 过滤容器化 NPM install 的工程参数、syscall 白名单与 CI/CD 集成清单。

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)。

核心实现步骤

  1. 生成 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": [...]},  // 限制子进程参数
        // 阻塞网络:无 "connect", "socket", "sendto"
        // 阻塞 exec:无 "execve"
        // 允许文件安装:openat(read-only /tmp/node_modules)
      ]
    }
    

    迭代测试:对污染包运行,验证阻塞 exfil(e.g., curl/webhook 失败,syscall 杀进程 SCMP_ACT_KILL)。

  2. 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 逃逸
  3. 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 阻塞设备。
  • 验证清单
    1. 模拟 Shai-Hulud:npm i 污染包,检查无网络流量(tcpdump)。
    2. TruffleHog 扫描输出:仅 mock 数据,无真实凭证。
    3. 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)

查看归档