人人都会AI编程

24.3 文件路径遍历漏洞与防护

更新时间:2026-07-11

文件路径遍历(Path Traversal),也常被称为目录穿越(Directory Traversal),是指攻击者通过构造特殊的文件路径(如 ../../etc/passwd),访问到应用程序预期目录之外的文件。这类漏洞在 Web 应用中臭名昭著,但在 Electron 桌面应用中同样致命,因为主进程通常拥有完整的文件系统读写权限,一旦被恶意利用,攻击者可以读取、覆盖甚至删除用户机器上的任意文件。

24.3.1 漏洞的典型场景

在 Electron 应用中,文件路径遍历最常出现在以下两种情况下:

1. 用户输入直接拼接文件路径

假设你的应用提供了一个“导出报告”功能,用户可以输入文件名,然后应用将文件保存到指定目录。粗心的代码可能是这样的:

// 主进程(不安全!)
ipcMain.on('export-report', (event, fileName) => {
  const fs = require('fs');
  const content = generateReport();
  // 直接拼接用户提供的文件名
  const filePath = `./reports/${fileName}`;
  fs.writeFileSync(filePath, content);
});

攻击者将文件名设为 ../../../../../../Windows/System32/drivers/etc/hosts,最终写入的路径就会变成 ./reports/../../../../../../Windows/System32/drivers/etc/hosts,经过路径解析后实际指向系统关键文件,造成严重破坏。

2. IPC 消息中携带不可信的文件路径

渲染进程可能向主进程发送一个“打开文件”的请求,并附上文件路径。如果主进程不进行校验就直接使用这个路径去读写文件,那么渲染进程中的任何代码(包括受 XSS 攻击注入的脚本)都能诱使主进程去操作任意文件。

// 主进程(不安全!)
ipcMain.handle('read-file', async (event, filePath) => {
  const fs = require('fs').promises;
  // 直接使用渲染进程传来的路径
  return await fs.readFile(filePath, 'utf-8');
});

此类问题在启用了 nodeIntegration 或通过不安全的 contextBridge 暴露了文件操作接口的应用中尤其危险。

24.3.2 真实的攻击示例

假设一个 Electron 应用提供一个笔记导出功能,用户可以选择导出的“文件夹名称”,应用会创建一个以该名称命名的文件夹并将笔记存入其中。攻击者输入文件夹名:

../../../.ssh

如果应用没有对输入做任何校验,最终会在当前工作目录(通常是应用的可执行文件所在目录或用户主目录)的上层创建 .ssh 文件夹,并可能在内部写入攻击者可控的内容,覆盖 SSH 密钥,从而达到远程控制用户机器的目的。

另一个常见的目标是覆盖应用自身的配置文件或更新脚本,劫持应用行为,这在具有自动更新功能的 Electron 应用中尤为致命。

24.3.3 防护措施

防止路径遍历漏洞的核心原则是:永远不要信任来自渲染进程(或任何外部源)的文件路径或文件名,始终进行验证和规范化。 具体的防护手段包括:

1. 使用 path 模块规范化路径并验证基础目录

Node.js 的 path.resolvepath.normalize 可以将包含 .. 的路径转换成绝对路径,去除任何回溯。然后通过检查规范化后的路径是否以预期的基目录开头,来杜绝穿越。

const path = require('path');
const fs = require('fs').promises;

// 安全的文件写入
ipcMain.handle('safe-export-report', async (event, fileName) => {
  const baseDir = path.resolve('/safe/reports'); // 绝对路径基目录
  const resolvedPath = path.resolve(baseDir, fileName);

  // 确保最终路径在 baseDir 内
  if (!resolvedPath.startsWith(baseDir + path.sep)) {
    throw new Error('非法的文件路径!');
  }

  await fs.writeFile(resolvedPath, content);
});

这段代码将用户输入的 fileName 解析为绝对路径,然后检查该绝对路径是否仍以 /safe/reports/ 开头(注意结尾加 path.sep 以避免类似 /safe/reports-fake 的绕过)。如果不在范围内,直接拒绝请求。

2. 限制允许的字符集

对于文件名类的输入,可以定义白名单正则表达式,只允许字母、数字、下划线、连字符等安全字符,彻底排除 ../\ 等敏感符号。

function isValidFileName(name) {
  // 只允许字母、数字、下划线和点号,长度 1-255
  return /^[a-zA-Z0-9_.-]{1,255}$/.test(name);
}

3. 避免直接暴露文件系统 API 给渲染进程

渲染进程不应直接拥有 fs 等模块的访问权限。即便使用 contextBridge,也只应暴露封装好的、经过严格参数校验的高级接口。例如,渲染进程只能调用 exportReport('reportName'),而具体的路径拼接和安全检查在主进程或 Preload 中进行。

4. 警惕解压缩和文件导入功能

如果你的应用支持解压文件(如导入模板包、主题),必须检查压缩包内每个条目的路径,防止 ZIP slip 漏洞。处理时可以先获取条目规范化后的目标路径,确认它在目标解压目录之下才写入。

5. 保持最小权限

即使主进程拥有完整权限,也应该在代码中主动把可操作的文件范围限制在必要的目录下(如应用的用户数据目录 app.getPath('userData')),避免对整个文件系统随意操作。这样即便存在漏洞,攻击面也会被极大地缩小。

24.3.4 结合 Electron 安全最佳实践

路径遍历的防御不应孤立存在。配合前文所述的安全上下文隔离、禁用 nodeIntegration、启用 sandbox 等基础安全设置,能够有效降低渲染进程被攻陷后所能造成的危害。在所有 IPC 通信中,将“验证输入、拒绝不合法请求”作为固定思维,是构建健壮 Electron 应用的关键步骤。

安全的文件操作本质上是不可信数据与文件系统之间的防火墙。理解这一漏洞的原理,并在每一个文件读写入口处都落实严格的校验,就能让你的 Electron 应用远离这类常见却高危的风险。