name: path-traversal-lfi description: 检测路径穿越和本地文件包含(LFI)风险;当目标存在文件读取/下载/预览功能且含路径参数时触发;适用于文件下载、日志查看、模板加载等场景。 when-to-use: 当目标存在文件读取/下载/预览功能且含路径参数(文件下载、日志查看、模板加载)时 allowed-tools: bash,read_file,list_files,rg user-invocable: false
路径穿越 / LFI 检测
成因引用
路径穿越成因:source(任何用户可控输入)→ sink(文件读取决策:os.Open / filepath.Join / include / 任何"决定打开哪个文件"的代码)。输入决定要打开/读取哪个文件,无路径规范化或白名单。不限于 /static/* FileServer,业务接口的 ?file= 同属此类。详见同根目录 pentest/web-security-testing/SKILL.md 漏洞成因图谱 · 路径穿越行(不在本 skill 重复成因)。
触发线索(基线检查项)
以下是已知的常见路径穿越触发线索,作为基线起点而非必检硬清单。核心原则:按"语义"识别,不按"参数名"识别——任何"外部输入决定服务端要打开/读取/包含哪个文件"的端点都属本范围。
基线触发线索按"sink 语义"分类:
- 业务文件读取接口(D-014 形态):
?file=/ 类似参数 +filepath.Join + os.Open,参数名可能完全不像"路径"——按代码逻辑判定 - 工单 / 订单 / 消息附件下载:
/api/<resource>/{id}/attachments/{filename}等业务路径 - 头像 / 图片 / 资源代理:
/img?src=...//proxy?url=...等 - 日志查看接口:
/admin/logs?file=...//api/logs/{name}等 - 模板 / 语言 / page 加载:
lang=/template=/page=/view=等 - 文档预览:office / pdf 文档预览功能调用文件路径
- 导入 / 导出功能:CSV / Excel 导出可能涉及临时文件路径
- 静态资源服务(
/static/*):FileServer 类入口——但禁止只测这一类就下"安全"结论 - 代码模式:后端代码出现
filepath.Join(uploadsRoot, userInput)+os.Open(...)/ioutil.ReadFile(...)/http.ServeFile(...)等"用户输入直接决定文件路径"形态
思考检查点
加载本 skill 时按这些问题思考:
- 这个端点的响应是文件流吗?Content-Type 是非 JSON 吗?后端语义是不是"读文件"?
- 同子系统其他文件读取入口是否同模式?(不限于"看起来像路径"的参数名)
- 跨子系统是否有同模式端点?
- 后端是否有
filepath.Join(...)/os.Open(...)等"输入决定文件"的形态?参数有没有经过filepath.Clean或realpath校验?
前置条件与安全边界
- 仅在授权环境测试
- 单参数最多 12 次请求
- 目标文件使用无害文件(
/etc/hostname、/etc/passwd、C:\Windows\win.ini) - 不尝试读取应用凭据文件(如数据库密码),仅证明穿越可行性
检测步骤
Step 1:基线与端点枚举
- 从端点账本(
recon-methodology产出的endpoint-ledger.jsonl)查询所有"响应是文件流 / Content-Type 非 JSON / 后端语义是读文件"的端点,逐入口独立测试 - 不限于参数名含
file/path/doc的端点——按 sink 语义而非参数名筛选 - 正常请求,记录基线响应
- 观察参数值的模式(相对路径、绝对路径、文件名)
Step 2:路径穿越探测
逐步测试穿越 payload:
| payload | 说明 |
|---|---|
../../../etc/passwd |
基本穿越 |
..%2f..%2f..%2fetc/passwd |
URL 编码绕过 |
....//....//....//etc/passwd |
双写绕过(过滤 ../ 时) |
..%252f..%252f..%252fetc/passwd |
双重编码绕过 |
/etc/passwd |
绝对路径直接访问 |
....\/....\/....\/etc/passwd |
反斜杠混合(Windows) |
Step 3:Windows 路径(若适用)
| payload | 说明 |
|---|---|
..\..\..\Windows\win.ini |
Windows 穿越 |
..%5c..%5c..%5cWindows\win.ini |
编码反斜杠 |
Step 4:LFI 到 RCE(仅评估可行性)
若确认文件包含存在(PHP 场景):
php://filter/convert.base64-encode/resource=index→ 源码泄露- 日志包含:确认日志路径可访问性
- 不实际执行 RCE,仅评估路径是否可行
示例库
正例形态(代码层根因)
f, _ := os.Open(filepath.Join(uploadsRoot, r.URL.Query().Get("file")))— D-014 形态:用户输入直接拼接到os.Open(filename-path-join-traversal-lfi)http.ServeFile(w, r, "/var/www/" + r.URL.Path)— 静态 ServeFile 不规范化路径(urlpath-servefile-traversal-lfi)tmpl := template.Must(template.ParseFiles("/views/" + page + ".html"))— 模板文件加载用户输入(page-parsefiles-traversal-lfi)include($_GET['view'] . '.php')— PHP LFI 形态(view-include-lfi)
窄化反例(必须避免)
以下是路径穿越维度的典型窄化误判:
- "只测了
/static/*FileServer 都被 path.Clean 拦截 → 全站无路径穿越" — 错。这是 D-014 误判的直接根源。FileServer 类入口与业务接口(?file=+filepath.Join + os.Open)是两类完全不同的入口,FileServer 安全不代表业务接口安全 - "参数名不像
file/path/doc→ 跳过路径穿越" — 错。按 sink 语义判定,不按参数名筛选。业务接口可能用attachment/name/id等参数名但实际是文件路径 - "已在某子系统命中路径穿越,其他子系统的同名端点不测了" — 错。跨子系统的隐式推广是漏报高发模式
- "响应是 PDF / 图片二进制 → 看不到文件内容 → 跳过" — 错。可通过 Content-Length 差异 / 错误响应(不存在文件→特定错误码)等侧信道判定,也可读取
/etc/passwd这类文本看是否能正常返回 - "参数被
filepath.Clean处理过 → 安全" — 错。Clean只规范化..但不阻止绝对路径,且不检查最终路径是否在允许目录内;必须配合filepath.Rel或白名单校验
反例义务(必须遵守)
为什么这里是「必须」:反例义务属于交付契约——"未发现路径穿越"或"path.Clean 拦截"结论是覆盖完整性的产物声明,缺失反向验证清单会让下游误信"该维度全站安全"。
写"未发现路径穿越"或"path.Clean 拦截"前,产物必须包含:
- 测过的文件读取入口完整清单(按 sink 语义枚举,不按参数名筛选;含业务文件接口 / 工单附件 / 头像代理 / 日志查看 / 模板加载 / 静态资源 / 文档预览等全部触发线索类别)
- 每个入口测过的 payload 类型(基本穿越 / URL 编码 / 双重编码 / 绝对路径 / Windows 路径等)
- 每个入口的响应证据(基线响应 / 穿越响应 / 文件内容确认)
清单不完整 → 结论降级为 partial-coverage 并显式声明未覆盖范围。
特别警示:只测 /static/* 而未测业务文件接口的,结论降级为 partial-coverage-only-static,禁止宣称"全站安全"——这是 redhaze-range D-014 误判直接根源。FileServer 类入口(Go http.FileServer 自动调 path.Clean)与业务接口(?file= + filepath.Join + os.Open 手动拼接)是两类完全不同的入口。
闭环验证要求(必须遵守)
通用闭环口径见同根目录 common/closure-verification.md(技能表 path 列同一抽取根下,需要时 read_file 读取)。核心:完整证据链才判 confirmed,中间信号最多 suspected。本漏洞特有要点:
- 仅凭"路径参数存在"不得判定漏洞,必须确认实际读取到目标文件内容
- 响应中需包含文件内容(如
/etc/passwd的用户列表格式)作为证据
判定标准
| 现象 | 判定 |
|---|---|
| 响应中包含目标文件的实际内容 | confirmed |
| 响应有差异(大小/状态码变化)但未泄露文件内容 | suspected |
| 路径被规范化或白名单限制,无法穿越 | not vulnerable |
| 只测了部分文件读取入口(如仅 FileServer 类),未覆盖业务文件接口 | partial-coverage-only-static(不得宣称 safe) |
修复建议
- 使用文件 ID 而非文件路径作为参数,通过映射表查找实际路径
- 对文件路径做规范化后校验(
realpath后检查是否在允许目录内) - Go 场景:
filepath.Clean后用filepath.Rel验证最终路径在允许目录内;或使用白名单 - 禁止
../和编码变体 - PHP 场景禁用
allow_url_include和allow_url_fopen