0x01:
文件上传漏洞起因于,上传程序没有对上传文件格式进行正确判断,导致可执行程序上传到网站目录。
常见的验证上传文件有两种:1.js本地验证,通过js获取上传文件后缀名,并和白名单比较,匹配则上传成功。由于js代码是本地验证,存在绕过风险(去除js代码,构造表单数据,直接绕过)。
2.后端程序验证,通过post数据到file_upload()函数,$_FILES['file']['type']判断上传程序后缀名.
function file_upload(){
$file_name = $_FILES['file']['name'];
$file_type = $_FILES['file']['type'];
$file_tmp = $_FILES['file']['tmp_name']; //临时文件存放位置
//判断文件后缀
问题1. if($file_type === 'image/jpg' || $file_type === 'image/jpeg'){ //jpg和jpeg类型,详见wiki
问题2. $file_new_type = '.jpg';
}
问题4. $file_name = substr(md5(time()),0,10);
move_uploaded_file($file_tmp,$file_name.$file_type) ;
}
关于PHP处理文件流,详见w3chool php file
问题1.$file_type获取文件后缀名,正常上传jpg时,$file_type ='image/jpg' 文件格式,上传php文件时,$file_type ='application/octet-stream'(任意二进制流)。这里的image/jpg,application/octet-stream属于MIME(Multipurpose Internet Mail Extensions),在浏览器里上传抓包时,有个content-type:image/jpg,值得就是header里MIME类型(问题3).
通过获取$file_type里的值,和白名单(image/jpg,image/jpeg)比较,如果值相同,说明后缀名是jpg(不考虑问题3)。
问题3(MIME头欺骗).既然获取MIME里的文件类型和白名单比较,通过修改数据包中Content-type:image/jpg(修改MIME类型),上传文件。
上传a.php,burpsuite截断数据包,修改content-type:application/octet-stream => image/jpg,然后转发。这时,$file_type='image/jpg',本来上传的php文件,获取的文件类型为二进制流,现在修改MIME后变成image/jpg。如果直接用$file_type作为文件的后缀名,就会造成a.php文件直接存储到网站目录中,造成getshell。
问题2.获取MIME类型为image/jpg,并不代表无法解决MIME欺骗问题,这里采用文件重命名,$file_type = jpg,那么$file_new_type = '.jpg';通过拼接文件名,即使上传的php文件,最后存储到网站目录里的也是jpg文件类型。
0x02:
文件上传漏洞产生来源于过滤不严,很多人采用strpos($str,'.')这种方式截取文件后缀名,然后就出现了xx.php;.jpg之类的畸形文件名(问题4)。最简单防护方式就是直接重命名文件名(全部),可能有人觉得问题2里,写多个判断语句会很麻烦,代码能安全一点,多写点不费事。
swicth($file_type){
case 'image/jpg':
$file_new_type ='.jpg';break;
case 'image/jpeg':
$file_new_type ='.jpeg';break;
....
default:
$file_new_type = false;
}
这里获取文件后缀名之后,重命名文件,有人担心,含有一句话代码的图片存储到网站里,不用考虑上传的是什么文件,当程序判断文件属于白名单里的后缀时,直接使用白名单里后缀写法,这样即使这个文件含有一句话,但服务器会把它当作图片处理,并不会执行里的代码(问题5)
文件名重命名一般采用:upload/时间/时间种子的MD5.后缀名
具体代码:
$file_save_directory = 'upload'.date('Y-m-d',time()); //命名存储文件夹名
if(!file_exists($file_save_directory)){
mkdir($file_save_directory); //如果不存在 创建文件夹
}
$file_new_name = substr(md5(time()),0,10).$file_new_type; //重命名文件名字
$result = move_uploaded_file($file_tmp,$file_save_directory.'/'.$file_new_name);
if($result){echo "upload success"}
0x03
php上传文件漏洞已经有很多年历史了,大牛前几天发出了个CVE-2006-7243的php空字符处理问题(webshell.cc),由于php处理a.phpx00.jpg时,会保存成a.php文件,导致畸形文件名的php文件能上传成功,有兴趣的可以搭环境测试下。
文件上传的同时伴随着文件名解析漏洞。IIS/Apache/Nginx,对畸形文件名处理错误,导致a.php;jpg/a.php .jpg(0x00截断)上传成功并解析
参考:wooyun php安全编码
http://zone.wooyun.org/content/1910 php上传缺陷