# 漏洞总结:PromptX @prompts/mcp-office 任意本地文件读取漏洞 ## 漏洞概述 - **漏洞标题**:Arbitrary Local File Read Vulnerability in @prompts/mcp-office of PromptX #571 - **漏洞类型**:CWE-862 (Improper Authorization) - **漏洞描述**:在 `packages/mcp-office/src/index.ts` 中发现任意本地文件读取漏洞。多个 MCP 工具(包括 `read_docx`, `read_xlsx`, `read_pptx`, `list_xlsx_sheets`, `read_pdf`)接受用户提供的路径参数,并直接在文件系统操作中使用,如 `fs.readFileSync` 和 `AmZip`,没有进行工作区边界检查或允许列表验证。攻击者可以通过提供绝对路径来读取工作区外的任何本地文件系统中的 Office 或 PDF 文件。 - **报告日期**:2026年4月10日 - **报告者**:Brucejin (brucejin@nju.edu.cn) ## 影响范围 - **受影响版本**: - 确认受影响的提交:`5cb0b51a63f9152ae079285ef39ccf268cb8151a` - 受影响范围:包含相同请求到文件系统路径列表的修订版本 - 修复版本:报告时不可用 - **安全影响**: - **机密性**:高(可以读取工作区外的任意本地 Office/PDF 文档) - **完整性**:无(演示的读取问题) - **可用性**:低(不正确的或昂贵的解析输入可能导致工具错误或资源消耗) - **范围**:已更改 ## 修复方案 ### 临时缓解措施 - 禁用或不需要的地方移除内置 `mcp-office` 服务器 - 限制工具调用给受信任的用户/代理 - 强制执行可读根允许列表,在任何文件系统访问之前 - 拒绝解析到明确批准工作区目录之外的路径 ### 推荐修复 - 消除上面记录的请求到文件系统路径 - 在用于使用前,规范化并解析请求的路径,然后验证它是否保持在批准的工作区或配置的允许根内 - 在符号链接解析后重新验证 - 重用已经在 `packages/mcp-workspace/src/service/workspace.service.ts` 中实现的边界检查模型 - 添加回归测试,证明攻击者控制的路径在批准的根之外无法到达 `fs.readFileSync` 或 `AmZip` ## POC 代码 ### 1. 复现请求 ```json {"jsonrpc":"2.0","id":"1","method":"tools/call","params":{"name":"read_docx","arguments":{"path":"/absolute-path-to-file.docx"}}} ``` ### 2. 验证步骤 ```bash # 构建和启动 @prompts/mcp-office,例如使用来自仓库根目录的 MCP Inspector npm install npm -filter @prompts/mcp-office build npx @modelcontextprotocol/inspector node packages/mcp-office/dist/index.js # 在仓库根目录外创建有效的 DOCK 文件 mkdir -p /prompts-poc printf 'PromptX arbitrary file read PoC\n' > /prompts-poc/secret.txt tldconvert -convert docx /prompts-poc/secret.txt -output /prompts-poc/secret.docx # 向 mcp-office 服务器提交 tools/call 请求 # 确认返回的工具输出包含 DOCK 文本内容,证明攻击者控制的路径超出了工作区到达了文件读取接收器 ``` ### 3. 技术根因代码 ```javascript // 1. js/file-access-from-request // Source: packages/mcp-office/src/index.ts:154 (request) // Sink: packages/mcp-office/src/index.ts:164 // Sink code: const buffer = fs.readFileSync(filePath); // 2. js/file-access-from-request // Source: packages/mcp-office/src/index.ts:154 (request) // Sink: packages/mcp-office/src/index.ts:175 // Sink code: const buffer = fs.readFileSync(filePath); // 3. js/file-access-from-request // Source: packages/mcp-office/src/index.ts:154 (request) // Sink: packages/mcp-office/src/index.ts:204 // Sink code: const zip = new AmZip(filePath); // 4. js/file-access-from-request // Source: packages/mcp-office/src/index.ts:154 (request) // Sink: packages/mcp-office/src/index.ts:239 // Sink code: const buffer = fs.readFileSync(filePath); // 5. js/file-access-from-request // Source: packages/mcp-office/src/index.ts:154 (request) // Sink: packages/mcp-office/src/index.ts:250 // Sink code: const buffer = fs.readFileSync(filePath); ```