一次性付费进群,长期免费索取教程,没有付费教程。
进微信群回复公众号:微信群;QQ群:460500587
教程列表 见微信公众号底部菜单 | 本文底部有推荐书籍微信公众号:计算机与网络安全
ID:Computer-network
在Web系统中,允许用户上传文件作为一个基本功能是必不可少的,如论坛允许用户上传附件,多媒体网站允许用户上传图片,视频网站允许上传头像、视频等。但如果不能正确地认识到上传带来的风险,不加防范,会给整个系统带来毁灭性的灾难。
在PHP项目中,提供上传功能并在服务器端未对上传的文件格式进行合理的校验是存在巨大风险的。如果恶意攻击者利用上传漏洞上传一些webshell,则可能完全控制整个网站程序,执行系统命令,获取数据库链接字串进行操作数据库等危险操作。
1、文件上传漏洞
以下是一个不安全的上传代码示例,即文件上传PHP接收代码upload.php。
$upload_dir='uploads'; // 用户上传文件保存目录
$upload_file=$upload_dir.basename($_FILES['userfile']['name']);
if(move_uploaded_file($_FILES['userfile']['tmp_name'],$uploadfile)) {
echo "恭喜您,文件上传成功";
} else {
echo "文件上传失败";
}
?>
以下是文件上传HTML代码upload.html。
请选择上传文件:
这是一个简单的上传文件功能,其中由用户上传文件,如果上传成功,保存文件的路径为http://服务器路径/uploads/文件名称。
如果攻击者上传一个如下内容的hacker.php脚本文件到服务器:
system($_GET['shell']);
?>
则攻击者就可以通过该文件进行URL请求http://服务器路径/uploads/hacker.php?shell=ls%20-al,从而可以执行任何shell命令。
图1所示是恶意脚本的执行结果,其中列出了该目录下的所有文件。
图1 上传漏洞造成的webshell执行结果
2、检查文件类型防止上传漏洞
上面例子中的代码非常简单,并没有进行任何的上传限制。如果要限制,通常的做法是限制文件上传类型。
下面在PHP代码中增加了文件类型限制来防止上传漏洞。
if($_FILES['userfile']['type']!="image/gif") {
die("请上传正确的文件类型");
}
$uploaddir='uploads';
$uploadfile=$uploaddir.basename($_FILES['userfile']['name']);
if(move_uploaded_file($_FILES['userfile']['tmp_name'],$uploadfile)) {
echo "恭喜您,文件上传成功";
} else {
echo "文件上传失败";
}
?>
在这种情况下,如果攻击者试图上传shell.php,则应用程序在上传请求中将检查文件MIME类型。以下是拒绝上传的HTTP请求返回数据包。
POST /upload.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE,close
Host: localhost:8080
User-Agent: Mozilla/5.0(Macintosh: Inter Mac OS X10_13_2)
AppleWebKit/537.36(KHTML,like gecko) Chrome/65.0.3325.181
Safari/537.36
Content-Type: multipart/form-data;boundary=xYzzY
Content-Length:32
--s76f8a7sf8as9f8a9f80as8df--
Content-Disposition: form-data;name="userfile";filename="shell.php"
Content-Type: text/plain
system($_GET['shell']);
?>
--s76f8a7sf8as9f8a9f80as8df--
HTTP/1.1 200 OK
Date: Thu, 31 May 2019 22:00:01 GMT
Server: Apache
X-Powered-By: PHP/5.6
Content-Length: 30
Connection: close
Content-Type: text/html
请上传正确的文件类型
这里成功地通过检测类型防止了非授权类型文件的上传,服务器拒绝接收文件。
但是如果只进行上传文件类型的检查也是不够的,攻击者通过修改POST数据包中Content-Type:text/plain字段为Content-Type:image/gif,然后发送数据包,即可成功实现恶意脚本的上传。
3、检查文件扩展名称防止上传漏洞
除了检查文件类型外,研发人员最常用的防范方法之一,就是基于白名单或者黑名单,验证所传文件的扩展名称是否符合。以下代码通过黑名单方式对文件类型进行限制。
$blacklist=array(".php","phtml",".php3",".php4"); // 黑名单
$uploaddir='uploads/';
$uploadfile=$uploaddir.basename($_FILES['userfile']['name']);
$item==substr($_FILES['userfile']['name'],-4);
if(in_array($item,$whitelist)) {
die("请上传正确的文件类型");
}
if(move_uploaded_file($_FILES['userfile']['tmp_name'],$uploadfile)) {
echo "恭喜您,文件上传成功";
} else {
echo "上传失败";
}
?>
以下是白名单模式限制文件类型的代码示例。
$whitelist=array(".jpg",".gif","png"); //白名单
$uploaddir='uploads/';
$uploadfile=$uploaddir.basename($_FILES['userfile']['name']);
$item==substr($_FILES['userfile']['name'],-4);
if(!in_array($item,$whitelist)) {
die("请上传正确的文件类型");
}
if(move_uploaded_file($_FILES['userfile']['tmp_name'],$uploadfile)) {
echo "恭喜您,文件上传成功";
} else {
echo "上传文件失败";
}
?>
从黑名单和白名单两种不同的验证方法来看,白名单方式绝对要比黑名单安全得多。但是,并不是说采用白名单方式验证就足够安全了。
IIS服务存在一个漏洞(Microsoft Internet Infomation Server 6.0 ISAPI Filename Analytic Vulnerability),如上传一个名为hacker.php;.gif的文件到服务器,PHP脚本文件因限制最后4个字符,所以本文件是合法的,但是当上传后浏览该文件——http://服务器路径/uploads/hacker.php;.gif时,就可以绕过Web程序的逻辑检查,从而能导致服务器以IIS进程权限执行任意恶意用户定义的脚本。此漏洞只针对于IIS特定版本。
在Apache程序中,同样存在一个由扩展名解析的漏洞。当恶意攻击上传一个有多个扩展名的PHP脚本文件时,如果最后的扩展名未定义,就会解析前一个扩展,比如hacker.php.2018文件。当将该文件上传时,如果是以白名单、黑名单方式进行验证,就可以绕过验证,上传非法文件到服务器,当浏览http://服务器路径/uploads/hacker.php.2018时,就会被当成PHP脚本执行。
4、文件上传漏洞的综合防护
以上例子说明,不可以只通过一种安全手段来阻止攻击者进行非法文件上传,应该同时综合应用检测文件类型、检查文件后缀、黑白名单、使用随机文件名称等多种方法进行防范。下面的代码是综合应用示例。
/**
* 生成随机字符串
* @param int $len
* $return string
*/
function genRandomString($len) {
$chars=array("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9");
$charsLen=count($chars)-1;
shuffle($chars); // 将精妙绝伦打乱
$output="";
for($i=0;$i<=$len;$i++) {
$output.=$chars[mt_rand(0,$charsLen)];
}
return $output;
}
$whitelist=array(".jpg",".gif","png"); //白名单
$item==substr($_FILES['userfile']['name'],-4);
if(!in_array($item,$whitelist)) {
die("请上传正确的文件类型");
}
if($_FILES['userfile']['type']!="image/gif") { // 校验文件MIME类型
die("请上传正确的文件类型");
}
$uploaddir='/tmp/uploads'; // 将用户上传的文件放到项目目录之外
$uploadfile=$uploaddir.genRandomString(20).$item; // 使用随机文件名
if(move_uploaded_file($_FILES['userfile']['tmp_name'],$uploadfile)) {
echo "恭喜您,文件上传成功";
} else {
echo "上传文件失败";
}
?>
验证上传文件的扩展名,以白名单、黑名单方式为主,但最好使用白名单。
除了在代码逻辑中防止上传漏洞外,同时也可以在项目部署时将上传目录放到项目工程目录之外,当作静态资源文件处理,并且对文件的权限进行设定,禁止文件的执行权限。
当用户上传文件到服务器保存时,一定要使用随机文件名进行存储,并保证所存储的扩展名合法。保证文件名的唯一性,也保证了存储的安全性,可以防止上传文件非法扩展进行解析。
微信公众号:计算机与网络安全
ID:Computer-network
【推荐书籍】