0x01 文件包含介绍
文件包含漏洞是代码注入的一种,就是用户注入一段能控制的脚本或代码,并让服务端执行
0x02 文件包含函数
php文件包含主要由下面4种函数来完成的
- include()
- include_once()
- require()
- require_once
当使用这4种函数包含一个新文件时,文件将作为php代码执行,php内核并不会在意被包含文件时什么类型,即使包含的时txt文件,图片文件。远程URL,也都将作为PHP代码执行
区别 require()的使用方法如:require(“xxx.php”)。这个函数通常放在PHP程序最前面,php程序执行前面,PHP程序再执行前,就会先读入require()所指定引入的文件,使他变成php程序网页的一部分
incloude()使用方法如:include(“xx.php”)。这个函数一般是放在流程控制的处理部分中。php程序网页再读到include()的文件时,才将它读进来,这种方式可以把程序执行时的流程简单化
require一个文件存在错误时,程序会中断执行,并显示致命错误 include一个文件存在错误时,那么程序不会中断,而是继续执行,并显示一个警告错误
require_once()、include_once()和require()、include()的区别是:当执行的问价被包含过 一次后,不会再次包含
0x03 文件包含影响函数
allow_url_include:是否允许远程包含文件 allow_url_include=On的情况下,它可以直接包含远程文件,当存在include(
v
a
r
)
且
(
var)且(
var)且(var)可控的情况下,可以直接控制$var变量来执行PHP代码。 allow_url_include再5.2.0以后默认配置为Off allow_url_fopen:是否允许打开远程文件 allow_url_fopen默认配置是On
0x04 从源码审计看远程文件包含
<?php
include("security.php");
include("security_level_check.php");
include("functions_external.php");
include("selections.php");
$language = "";
if(isset($_GET["language"]))
{
switch($_COOKIE["security_level"])
{
case "0" :
$language = $_GET["language"];
break;
case "1" :
$language = $_GET["language"] . ".php";
break;
case "2" :
$available_languages = array("lang_en.php", "lang_fr.php", "lang_nl.php");
$language = $_GET["language"] . ".php";
break;
default :
$language = $_GET["language"];
break;
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<!--<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Architects+Daughter">-->
<link rel="stylesheet" type="text/css" href="stylesheets/stylesheet.css" media="screen" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon" />
<!--<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>-->
<script src="js/html5.js"></script>
<title>bWAPP - Missing Functional Level Access Control</title>
</head>
<body>
<header>
<h1>bWAPP</h1>
<h2>an extremely buggy web app !</h2>
</header>
<div id="menu">
<table>
<tr>
<td><a href="portal.php">Bugs</a></td>
<td><a href="password_change.php">Change Password</a></td>
<td><a href="user_extra.php">Create User</a></td>
<td><a href="security_level_set.php">Set Security Level</a></td>
<td><a href="reset.php" onclick="return confirm('All settings will be cleared. Are you sure?');">Reset</a></td>
<td><a href="credits.php">Credits</a></td>
<td><a href="http://itsecgames.blogspot.com" target="_blank">Blog</a></td>
<td><a href="logout.php" onclick="return confirm('Are you sure you want to leave?');">Logout</a></td>
<td>
<font color="red">
Welcome <?php
if(isset($_SESSION["login"])) {
if(isset($_SESSION["login"])) {
echo ucwords($_SESSION["login"]);
};
}
?>
</font>
</td>
</tr>
</table>
</div>
<div id="main">
<h1>Remote & Local File Inclusion (RFI/LFI)</h1>
<form action="<?php echo($_SERVER["SCRIPT_NAME"]);?>" method="GET">
Select a language:
<select name="language">
<?php
if($_COOKIE["security_level"] == "1" || $_COOKIE["security_level"] == "2")
{
?>
<option value="lang_en">English</option>
<option value="lang_fr">Fran?ais</option>
<option value="lang_nl">Nederlands</option>
<?php
}
else
{
?>
<option value="lang_en.php">English</option>
<option value="lang_fr.php">Fran?ais</option>
<option value="lang_nl.php">Nederlands</option>
<?php
}
?>
</select>
<button type="submit" name="action" value="go">Go</button>
</form>
<br />
<?php
if(isset($_GET["language"]))
{
if($_COOKIE["security_level"] == "2")
{
if(in_array($language, $available_languages)){
include($language);
}
} else
{
include($language);
}
}
?>
</div>
<div id="side">
<a href="http://twitter.com/MME_IT" target="blank_" class="button"><img src="./images/twitter.png"></a>
<a href="http://be.linkedin.com/in/malikmesellem" target="blank_" class="button"><img src="./images/linkedin.png"></a>
<a href="http://www.facebook.com/pages/MME-IT-Audits-Security/104153019664877" target="blank_" class="button"><img src="./images/facebook.png"></a>
<a href="http://itsecgames.blogspot.com" target="blank_" class="button"><img src="./images/blogger.png"></a>
</div>
<div id="disclaimer">
<p>bWAPP is licensed under <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/" target="_blank"><img style="vertical-align:middle" src="./images/cc.png"></a> © 2014 MME BVBA / Follow <a href="http://twitter.com/MME_IT" target="_blank">@MME_IT</a> on Twitter and ask for our cheat sheet, containing all solutions! / Need an exclusive <a href="http://www.mmebvba.com" target="_blank">training</a>?</p>
</div>
<div id="bee">
<img src="./images/bee_1.png">
</div>
<div id="security_level">
<form action="<?php echo($_SERVER["SCRIPT_NAME"]);?>" method="POST">
<label>Set your security level:</label><br />
<select name="security_level">
<option value="0">low</option>
<option value="1">medium</option>
<option value="2">high</option>
</select>
<button type="submit" name="form_security_level" value="submit">Set</button>
<font size="4">Current: <b><?php echo $security_level?></b></font>
</form>
</div>
<div id="bug">
<form action="<?php echo($_SERVER["SCRIPT_NAME"]);?>" method="POST">
<label>Choose your bug:</label><br />
<select name="bug">
<?php
foreach ($bugs as $key => $value)
{
$bug = explode(",", trim($value));
echo "<option value='$key'>$bug[0]</option>";
}
?>
</select>
<button type="submit" name="form_bug" value="submit">Hack</button>
</form>
</div>
</body>
</html>
上面是bWAPP的源码:直接定位到文件包含函数include($language),靶机上文件包含分为low、medium、high三个等级 
当等级为low时,security_level等于0
 从上图可以看出当security=0时,$language值就等于get方式传入的值,
 经过一系列跳转来到147行,if(isset(
G
E
T
[
"
l
a
n
g
u
a
g
e
"
]
)
)
条
件
为
真
,
进
入
158
行
执
行
i
n
c
l
u
d
e
(
)
函
数
,
可
以
看
出
全
程
没
有
过
滤
这
样
_GET["language"]))条件为真,进入158行执行include()函数,可以看出全程没有过滤 这样
G?ET["language"]))条件为真,进入158行执行include()函数,可以看出全程没有过滤这样language参数可控,可以直接进行文件包含
当等级为medium时,security_level等于1时
 中级时他会在传入的$language后面拼接一个.php后缀,然后在147行进行if判断后,执行158行的文件包含 例如传入的是$language=./1.txt文件 执行include()时为include(./1.txt.php)造成无法包含成功
解决办法 1 %00截断
php内核是C语言实现的,因此C语言中的一些字符串处理函数。在连接字符串时,0字节(\x00)将作为字符串结束符。所以只要在1.txt后面加入一个0字节,就能截断$language变量后的字符串 即$language=./1.txt%00这样就能把添加的代码34行拼接的 **".php"**截断了
利用条件
- magic_quotes_gpc=off
- php小于5.3.4
从利用条件来看可知利用比较局限
防御
在一般的应用 中,0字节是用户不需要的,可以完全禁用0字节
<?php
function getVar($name){
value =isset(GET[name] ? GET[$name] : null;
if(is_string($value)){
value= str_replace(“\0”, ‘ ‘ , value);
}
}
?>
可以看到这里对“\0"进行了一个简单过滤
解决方法2 构造长目录截断(我尝试没有成功,可以自行尝试一下)
如果使用php5.3.4以上,或者过滤了"\0",大佬说可以利用操作系统对目录最大长度的限制,可以不需要0字节而达到截断的目的
目录字符串在windows下256字节,在linux下4096字节达到最大值,最大值长度以后的字符串将被丢弃 所以可以利用【./】构造超长目录,这样就可以把后面拼接的".php"给去掉了,但是我在bWAPP上调试时发现没用,这里我用的是win10+phpstudy搭建环境来尝试的
当等级为high时,security_level=2
 可以看到他定义一个数组变量 $available_languages,里面存放了lang_en.php、lang_fr.php、和lang_nl.php三个值,并且将GET传参得到的language的值后面拼接了“.php”后重新赋值给$language, 然后执行如下if判断  此时security_level==2会执行149的if判断,判断$language的值是否在数组$available_languages中,如果在,执行include()包含,如果不在直接跳到160行外,不进行文件包含操作
0x05 利用伪协议进行文件包含
文件包含除了本地文件包含和远程文件包含还可以结合伪协议进行使用,关于伪协议的介绍以及使用我会单独写一篇文章的
|