配图

当 AI Agent 需要读写本地文件时,路径规范化(Path Normalization)是安全边界的第一道防线。未经处理的 ../../../ 式路径跳跃可能直接绕过沙箱,本文给出上线前必须核查的 7 类风险点与自动化检测方案。


为什么路径规范化≠简单字符串替换?

开发者在处理 data/../../etc/passwd 这类路径时,常犯三个错误: 1. 仅做字符串匹配:用正则删除 ../ 但未解析实际路径层级,导致 data/././../../ 仍可逃逸 2. 未绑定根目录:未用 realpath()Path.resolve() 锁定基准目录,相对路径仍可回溯 3. 忽略符号链接:软链接可能指向沙箱外,需配合 O_NOFOLLOW 标志或 canonicalize_path


审计清单(Linux/macOS 示例)

1. 基准目录锁定

  • [ ] 使用 chroot()openat(dirfd) 限定工作目录
  • [ ] 检查所有文件 API 是否传递绝对路径(禁止拼接用户输入的相对路径)
  • 技术细节
  • chroot() 需配合 chdir() 使用,且要求进程有 CAP_SYS_CHROOT 能力
  • openat()dirfd 应来自预先打开的基准目录文件描述符
  • 对于容器化场景,需检查挂载点是否允许 .. 回溯到宿主机

2. 规范化函数选择

  • [ ] Pythonpathlib.Path.resolve(strict=True) > os.path.abspath
  • [ ] Node.jsfs.realpath.native + path.join 替代手动拼接
  • [ ] Ruststd::fs::canonicalize 但需处理 ErrorKind::NotFound
  • 边界案例
  • Windows 下 Path.resolve() 可能保留大小写(NTFS 不敏感但审计日志需一致)
  • Node.js 的 fs.realpath.native 在 ARM 架构的 Alpine Linux 上可能有性能问题

3. 符号链接防御

  • [ ] 关键操作添加 O_NOFOLLOW(open 系调用)
  • [ ] 或用 lstat() 先检查文件类型(注意 TOCTOU 风险)
  • 深度防御
  • 结合 faccessat2()RESOLVE_BENEATH 标志(Linux 5.6+)
  • 对于临时文件,应使用 mkstemp() 而非手动创建(避免竞态条件)

4. 沙箱逃逸测试用例

# 必须覆盖的测试路径样本
test_cases = [
    "config/../../.ssh/id_rsa",  # 向上跳转
    "/tmp/symlink→/etc/passwd",  # 符号链接
    "a/././b/../../etc",        # 冗余当前目录
    "%2e%2e/%252e%252e%252f/etc" # URL 编码绕过
]
- 扩展测试集: - Unicode 规范化攻击:\u202e 等双向控制字符 - 超长路径(超过 PATH_MAX 时各语言处理差异) - 设备文件路径(如 /proc/self/mem

5. 错误处理

  • [ ] 禁止将 realpath() 的错误信息原样返回用户(泄露路径结构)
  • [ ] 统一转换为 PermissionDenied 类泛化错误
  • 日志策略
  • 生产环境应记录规范化前后的哈希值而非原始路径
  • 对高频失败IP启用人机验证(防暴力探测)

6. 审计日志字段

  • [ ] 记录原始输入路径与规范化后路径
  • [ ] 对多次规范化失败的行为触发熔断
  • 可观测性增强
  • 在 Prometheus 中监控 path_normalization_failures 指标
  • 通过 eBPF 跟踪 openat 系统调用

7. 运行时加固

  • [ ] 使用 Landlock 或 AppArmor 限制文件树访问范围
  • [ ] 对 Python 等动态语言需审计 __import__('os').system 调用
  • 进阶方案
  • gVisor 的 Gofer 协议可拦截所有文件操作
  • 对于敏感操作,通过内核模块实现路径白名单

工具推荐与集成

静态检测工具链

  1. Semgrep
  2. 预置规则检测 os.path.join 与用户输入拼接
  3. 自定义规则识别未处理的 ../ 模式
  4. CodeQL
  5. 构建数据流分析追踪路径传播
  6. 识别未经验证的 FileInputStream 构造参数

动态测试方案

  • Firejail 沙箱
    firejail --private-tmp --trace=open,openat \
      -- ./your_agent --input=user_controlled_path
  • 通过日志分析实际访问的文件范围
  • Ptrace 监控
  • 使用 strace -e file 过滤文件系统调用
  • 对容器内进程需附加 nsenter 命令

CI/CD 集成示例

# GitHub Actions 示例
jobs:
  path_audit:
    steps:
      - uses: returntocorp/semgrep-action@v1
        with:
          config: p/security-audit
      - run: |
          python -m pytest tests/test_path_normalization.py \
            --junitxml=report.xml
          firejail --trace=open \
            -- ./agent --test-file-access

历史漏洞案例研究

  1. CVE-今年-4034(Polkit pkexec)
  2. 通过非规范化路径加载恶意库
  3. 修复方案:execve() 前强制 PATH 解析
  4. Python 打包目录穿越(今年)
  5. tarfile.extract() 未处理恶意压缩包路径
  6. 现默认启用 filter='data' 参数
  7. Node.js fs.realpath 缓存污染(今年)
  8. 缓存未区分大小写导致绕过检查
  9. 现提供 fs.realpath.native 规避

终极建议:在 Agent 设计早期即引入「最小文件权限」原则,所有文件访问应显式声明所需操作模式(如只读、追加),并通过 Landlock 等机制在内核层强制执行。对于金融、医疗等敏感场景,建议采用二次验证机制——当检测到路径包含 .. 或符号链接时,强制通过人工审批通道执行操作。

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐