Web Security | File Upload
目录
概念
网站通常拥有很多可以上传文件的接口供用户使用,比如QQ空间里面的日志、说说、头像上传等.如果上传规则不严格,就产生了漏洞,黑客就可以通过上传ASP、PHP可执行的后门文件来控制网站(网页后门文件也叫做" webshell ")
加固
针对" File Upload(文件上传) "攻击的加固包括" 软件 "层面,包括" 硬件 "层面;包括" 网络层面 ",包括" 架构层面 ";包括" 代码层面 ",包括" 网站层面 "等等。对攻击的防护,就像" 什么是安全 ",没有绝对、没有尽头
PHP加固
在PHP层面,针对" File Upload(文件上传) "攻击手段的加固,包括" 覆写保护 "、" 上传方式规范 "、" 代码层防护 "、" 文件名处理 "、" 文件内容处理 "、" 输出形式规范 " 在内的六个方向
覆写保护
使用" file_exists(string $path) "方法来检测目标文件或目录是否存在,以防止被覆盖
file_exists(String $path)
$file=$_FILES['file'];
if(file_exists("/upload/".$file['name']))
{
die("<script>alert('文件已存在')</script>")
}
上传方式规范
使用" move_uploaded_file "函数检查并确保目标文件是否为合法(HTTP POST)上传的文件,若目标合法(是POST传进来的文件),则将目标文件移动到指定位置
move_uploaded_file(String $filename,String $destination) //合法返回" True "并移动目标文件,不合法则返回" False ",不做任何操作
$raw_file=$_FILES['file'];
$raw_name=$raw_file['name'];
$tmp_name=md5($raw_name);
if(!move_uploaded_file($raw_name,"/uploads/tmp/$tmp_name"))
{
die("<script>alert('上传失败,上传方式不合法')</script>");
}
代码层防护
代码层防护包括" 字符串处理 "、" 算法健壮优化 "两部分
字符串处理
字符串处理是在代码层面,对代码字符串进行" 编码转换 "、" 大小写转换 "、" 敏感字符过滤 "、" 格式化处理 "共四个方面的加固处理
编码转换
编码转换是对传入的目标字符串进行对应的编码转换,防止攻击者借助编码问题进行攻击绕过
iconv(String $in_charset,String $out_charset,String $str) /*源编码,目标编码,被操作字符串*/
iconv("gdb2312","UTF-8",$tmp_name);
大小写转换
规范大小写,防止绕过代码注入
strtolower($string);
strtoupper($string);
敏感字符过滤
" strstr() "函数用来查找内容在目标字符串是否存在
strstr(string,search)
if(strstr($tmp_name,'PHP'))
{
die();
}
" in_array() "函数用于在目标中搜索是否存在指定数组的内容
in_array(search,array)
$deny=array(".PHP",".PHP5",".PHP4",".PHP3",".PHP2",".html",".htm",".phtml",".pht",".PHP",".PHP5",".PHP4",".PHP3",".PHP2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
if(in_array($deny,$tmp_name))
{
die();
}
" strrchr() "函数用来查找字符在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符
可用" strrchr() "函数来取出" . "号后面的文件类型字符串
strrchr(string,char)
$tmp_name=strrchr($tmp_name,'.');
" str_replace(区分带小写) "与" str_ireplace(不区分大小写) "用来替换目标字符串中的字符(串)
str_replace(find,replace,string)str_ireplace(find,replace,string)$tmp_name=str_ireplace('::$DATA','',$tmp_name);
格式化处理
格式化处理包括" 去除空格 "," 去除回车"," 去除空格 "," 去除垂直指标符 "," 去除NULL "等
" trim() "函数方法可以去除目标字符串中的" 空白符 "、" 换行符 "、" 制表符 "、" 垂直制表符 "、" 回车 "、" 空格 "、" NULL "等
trim($string);
算法健壮优化
算法健壮优化是在代码层面,对程序的流程算法进行优化,抵御逻辑漏洞,提高健壮性
文件名处理
对文件名的处理,为了防止恶意利用等攻击手法,遂将文件名通过" md5() "、" uniqid() "函数进行随机化后重新命名再存储
" md5() "用来计算目标字符串的MD5散列," uniqid() "用来生成一个基于当前时间的唯一ID
md5($string)uniqid()md5(uniqid().$tmp_name);
文件内容处理
对接收到的上传文件,为避免存在" 图片马 "等恶意内容的存在,遂将图片进行二次渲染,重新生成一张图片或文件
" imagecreatefromjpg() "函数与" imagecreatefrompng() "函数可以分别生成" jpg "、" png "两种形式的图片
imagecreatefromjpg(image,file)imagecreatefrompng(image,file)$img=imagecreatefromjpg($tmp_file);
" imagedestroy() "用来销毁图像生成函数生成的图片对象
imagedestroy(imagecreatefromfile)$img=imagecreatefromjpg($tmp_file);imagedestroy($img);
输出形式规范
" imagejpg() "函数与" imagepng() "函数可以分别以" jpg格式 "、" png格式 "的形式将图像输出到浏览器或文件
imagejpg(image,filename) /*" image "是欲输出的图像资源(imagecreate,imagecreatefrom)*/imagepng(image,filename)imagejpg(imagecreatefromjpg($tmp_file),$file);
方法总结
·isset(var) | 检测目标(变量)是否初始化·file_exists(path) | 检查文件或目录是否存在·in_array(search,array) | 查询数组中是否存在指定的值
·iconv(string $in_charset,string $out_charset,string $str) | 将字符串按要求字符编码转换·md5(string) | 计算字符串的MD5散列·uniqid(参数可选) | 基于微妙记的当前时间(生成一个唯一ID)
·str_replace(find,replace,string) | 替换目标字符串的字符(区分大小写)·str_irepalce(find,replace,string) | 替换字符串中的字符(不区分大小写)·strstr(string,search) | 搜索字符串在令一字符串中是否存在·strrchr(string,char) | 查找字符在另一个字符串中最后一次出现的位置,并返回从该位置字符串结尾的所有字符·trim(string) | 去除字符串两侧的空白字符及其他预定义字符(换行、制表、NULL、垂直制表符、回车、空格)·strtolower(string) | 字符串转换为小写·strtoupper(string) | 字符串转换为大写
·move_uploaded_file(file,newloc) | 检测并移动post传进来得目标文件(仅用于)到新的位置,目标存在则覆盖·getimagesize(string $filename) | 用于获取图像大小及相关信息(按0.1.2.3...数组形式索引,2为大小)·imagecreatefromjpeg(string $filename) | 由文件或URL创建一个新图像·imagecreatefrompng(string $filename) | 由文件或URL创建一个新图像·imagejpg(image,filename) | 以JPG格式将图像输出到浏览器或文件"image"是欲输出得图像资源(imagecreate,imagecreatefrom)·imagepng(png,filename) | 以PNG格式将图像输出到浏览器或文件"png"是欲输出得图像资源(imagecreate,imagecreatefrom)imagedestroy(image) | 销毁图像(针对图像创建函数返回的标识符)·basename(path) | 返回路径中文件名称的部分sys_get_temp_dir() | 返回临时目录的路径unlink(filename) | 用于删除文件
代码示例
$_FILES对文件流的操作 name type size tmp_name error" getimagesize() "函数用于得到目标多媒体文件的信息basename(path) | 返回路径中文件名称的部分sys_get_temp_dir() | 返回临时目录的路径unlink(filename) | 用于删除文件DIRECTORY_SEParaTORint_get() | 获取一个配置选项的值if ($_FILES['file']['error']) { die();}$filter = array(".jpg", '.png', '.gif');//过滤器if ($_FILES['file']) { $arr = $_FILES['file']; if (file_exists(TPMELATE."/upload/".$arr['name'])) //检测是否存在 { echo "<script>alert('该文件已经存在')</script>"; } else { $filename = iconv("UTF-8","gb2312",TPMELATE."/upload/".$arr['name']); move_uploaded_file($arr["tmp_name"],$filename); echo $filename;die(); }} if (!in_array($file_ext, $filter)){ echo "<script>alert('error')</script>"; die(); if (strstr($file_ext, "PHP")) { $file_ext = str_replace("PHP", "", $file_ext); }}if (isset($_POST['submit'])) { if (file_exists(TPMELATE."/upload/")) { $deny_ext = array(".PHP",".PHP5",".PHP4",".PHP3",".PHP2",".html",".htm",".phtml",".pht",".PHP",".PHP5",".PHP4",".PHP3",".PHP2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess"); $file_name = trim($_FILES['file']['name']); $file_ext = strrchr($file_name, '.'); $file_ext = strtolower($file_ext); //转换为小写 $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA $file_ext = trim($file_ext); //首尾去空 if (!in_array($file_ext, $deny_ext)) { $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = TPMELATE."/upload/".'/'.$file_name; if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '此文件类型不允许上传!'; } } else { $msg = TPMELATE."/upload/" . '文件夹不存在请手工创建!'; },}if (file_exists($uploaddir)) { if (($_FILES['upfile']['type'] == 'image/gif') || ($_FILES['upfile']['type'] == 'image/jpeg') || ($_FILES['upfile']['type'] == 'image/png') || ($_FILES['upfile']['type'] == 'image/bmp') ) { if (move_uploaded_file($_FILES['upfile']['tmp_name'], $uploaddir . '/' . $_FILES['upfile']['name'])) { echo '文件上传成功,保存于:' . $uploaddir . $_FILES['upfile']['name'] . "\n"; } } else { echo '文件类型不正确,请重新上传!' . "\n"; } } if( ( strtolower( $uploaded_ext ) == 'jpg' || strtolower( $uploaded_ext ) == 'jpeg' || strtolower( $uploaded_ext ) == 'png' ) && ( $uploaded_size < 100000 ) && ( $uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png' ) && getimagesize( $uploaded_tmp ) ) {if( $uploaded_type == 'image/jpeg' ) { $img = imagecreatefromjpeg( $uploaded_tmp ); imagejpeg( $img, $temp_file, 100); } else { $img = imagecreatefrompng( $uploaded_tmp ); imagepng( $img, $temp_file, 9); } imagedestroy( $img );
// Delete any temp filesif( file_exists( $temp_file ) ) unlink( $temp_file );
if( !move_uploaded_file( $uploaded_tmp, $target_path ) #can we move the file to the upload folder// Where are we going to be writing to?$target_path = DVWA_WEB_PAGE_TO_ROOT . 'hackable/uploads/';//$target_file = basename( $uploaded_name, '.' . $uploaded_ext ) . '-';$target_file = md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;$temp_file = ( ( ini_get( 'upload_tmp_dir' ) == '' ) ? ( sys_get_temp_dir() ) : ( ini_get( 'upload_tmp_dir' ) ) );$temp_file .= DIRECTORY_SEParaTOR . md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
攻击
漏洞多发位置
黑名单和白名单
在进行上传过滤时,管理员设置了特定的几个文件类型不能被上传,其余文件类型都能被上传,这种配置的叫做黑名单
在进行上传过滤时,管理员设置了只允许特定的几个文件类型能被上传,其余文件类型都不能上传,这种配置叫做白名单
判断黑白名单
上传一个拥有完全不存在文件类型的文件,若被拦截,则说明使用的是白名单,反之,使用的是黑名单
前端与后端
这里的前端是指用户所能看到的页面,后端则指的是服务器网站管理后台、数据库等用户看不到的代码页面或程序
前端的检测和防护主要使用JS来实现,所以客户端(前端)检测与绕过也称为Js检测与绕过
前端检测原理
调用JS函数,将文件名转换为小写,通过substr()获取文件名最后一个点号后面的后缀,对文件类型进行后缀名判断
后端检测原理
通过strtolower()函数,将上传文件名改为小写后,再进行判断,规避了大小写绕过
通过str_ireplace()函数,不分大小的去替换掉上传文件名中的字符
通过检测传输数据包中的Content_Type来检测mime,判断文件类型是否合法
通过getimagesize()函数来获取上传图片的宽高等信息,若不是图片则获取不到信息
过滤上传文件的主要方向
文件后缀
文件类型
过滤上传文件的主要手段
Javascripts脚本扩展名检测
服务器端扩展名检测
MIME-type文件类型检测Content-type
文件幻数检测
文件大小限制
媒体文件重渲染
多媒体池,多媒体文件映射,多媒体独立服务器
前端过滤绕过
前端的过滤主要是由Javascripts脚本来进行过滤的,因为脚本处于客户端,所以只要在浏览器向服务器发包前,将脚本删除或更改就可以绕过前端过滤了
若不想对JS脚本进行更改的同时进行绕过,就需要了解,前端脚本是黑名单过滤还是白名单过滤了
先上传合法的后缀名文件,再使用抓包工具,更改数据包内容,将上传文件的合法后缀名变更为指定后缀名
伪造表单传递参数
针对黑名单
大小写混淆绕过,若甲方过滤了.PHP/.asp后缀名的文件,我们可以试试.PHP/.PHP/.asP/.ASP等
偏门扩展名绕过,若甲方过滤了.PHP/.asp后缀名的文件,我们可以试试.PHP5/.PHP3/.PHP4/.exe/.exee/.jsp/.jspx/.jspf/.asa/.cer等
针对白名单
截断上传绕过,假设甲方过滤了.PHP/.asp后缀名的文件,我们可以使用00截断的方式进行绕过,00截断的原理是,当文件系统读取到"0x00"这个字符时,会认为文件已经结束,所以不再向后 读取,与此同时真正的后缀名又满足他的过滤条件,这样就可以绕过过滤,列如想上传.PHP类型的文件,我们可以试试上传1.PHP.jpg,之后抓包,对其进行改包,将.jpg的"."改为0x00对其进行绕过(PHP版本需要小于等于5.3.4)
后端过滤绕过
通常围绕着文件上传的难题,主要围绕的就是后端过滤。通常前端过滤只是为了可以筛掉大部分垃圾文件,为服务器均衡性能。后端过滤才是真正主要的过滤方式
大小写混淆绕过,若甲方过滤了.PHP/.asp后缀名的文件,我们可以试试.PHP/.PHP/.asP/.ASP等
偏门扩展名绕过,若甲方过滤了.PHP/.asp后缀名的文件,我们可以试试.PHP5/.PHP3/.PHP4/.phtml/.exe/.exee/.jsp/.jspx/.jspf/.asa/.cer
截断上传绕过,假设甲方过滤了.PHP/.asp后缀名的文件,我们可以使用00截断的方式进行绕过,00截断的原理是,当文件系统读取到"0x00"这个字符时,会认为文件已经结束,所以不再向后 读取,与此同时真正的后缀名又满足他的过滤条件,这样就可以绕过过滤,列如想上传.PHP类型的文件,我们可以试试上传1.PHP.jpg,之后抓包,对其进行改包,将.jpg的"."改为0x00对其进行绕过(PHP版本需要小于等于5.3.4)
windows特性绕过,因为windows系统会自动去掉不符合规则的符号,所以当我们的文件后缀名不符合windows文件命名规则,却不被网站上传规则限制,可以上传成功时,那我们上传的文件就会被windows自动去掉不合规的字符,从而正常的读写上传的文件。我们可以通过在文件名后加点'.',在文件名后加空格' ',在文件名后加冒号':'再补上合法的文件名,又或者再文件名后加'::$DATA'以及'::$DATA......'来绕过,并正常读写(shell.PHP:1.jpg)
先上传合法的后缀名文件,再使用抓包工具,更改数据包内容,将上传文件的合法后缀名变更为指定后缀名
若甲方使用MIME-type来检测文件类型,所以我们需要使用改包工具将发送的数据进行更改,将Content-type的内容改为可上传文件的文件类型,image/gif[gif图],image/jpeg[jpeg],image/png[png图片],application/msword[word文档],text/plain[纯文本]
若甲方检测文件头,我们则需要在上传的文件中加上16进制的文件幻数头,让其看上去就像是某种特定格式的文件
-
JPG格式文件幻数头:FF D8 FF E0 00 10 4A 46 49 46 00
-
PNG格式文件幻数头:89 50 4e 47 0d 0a 1a 0a 00 00 00
-
GIF格式文件幻数头:47 49 46 38 39 61 00 04 00 03 f7
//GIF有一特殊性质,在文件开头直接写入adc编码的GIF89a字符串,就可以编程GIF类型的文件
图片马,甲方可能不止会检测文件幻数头,还会检测文件内容,所以我们可以使用一句话木马和图片合成的图片马。可以使用copy命令在命令提示符窗口将图片文件和一句话木马复制并合成一个新的文件:copy1.jpg/b+2.PHP/a 3.jpg
copy工具中,/b是指将文件以二进制(Binary)复制,/a是指将文件以ASC编码来进行复制
分布式配置文件上传绕过
若甲方上传类型卡的很严格,不妨试试.htaccess文件突破上传。.htaccess文件全名叫分布式配置文件,他的作用等同于一个mime配置文件,他控制他所在目录及其子目录的所有文件
若管理员配置不当,没有进行限制,我们就可以通过上传一个文件名为.htaccess的文件来重新定义目标目录下,所上传的文件都当作什么格式的文件去进行解释,这样可以直接上传真正的合法文件,但是通过.htaccess文件将合法的文件通过特定解释器去解释。我们需要在上传的.htaccess文件中写入配置内容
配置内容:AddType application/x-httpd-PHP .jpg意思是,jpg文件都用PHP解析,mime语法
配置内容:AddHandler PHP5-script .PHP意思是只要文件名中包括.PHP,那么.htaccess就会将其用PHP解释器进行解析
配置内容:<FilesMatch "123">SetHandler application/x-httpd-PHP意思是匹配文件名,若文件名包括字符串123,则将其文件当作PHP文件处理
富文本编辑器攻击(RTE)
富文本编辑器运用非常广泛,qq的说说,百度的贴吧输入框,都是富文本编辑器,它的作用是让人们在网上写文档,就像在world里面写文档一样,且不需要写文档的人懂HTML代码。但就是因为像world文档一样可以添加图片,所以才会有漏洞。我们可以尝试攻击富文本编辑器,尝试找出期间的文件上传漏洞
二次渲染
如果甲方有类似于二次渲染的的技术,我可以尝试通过攻击文件加载器来进行上传websehll
条件性漏洞
又称解析漏洞,是依靠版本的漏洞
PHP 5.3.4 解析漏洞
原理:小于或等于5.3.4版本的PHP,会产生00截断漏洞
列子:PHP会将webshell.PHP%00.jpg解析成webshell.PHP
apache 1.x/2.x 解析漏洞
原理:apache解析文件的规则是从右到左开始判断解析,如果后缀名为不可识别文件解析,就在往左判断。
列子:apache会将webshell.PHP.kkk解析成webshell.PHP
apache 1.x/2.x 配置漏洞
原理:若在apache的conf配置文件里面配置了AddHandler PHP5-scripts.PHP,这时只要文件名里面包含.PHP,就会以PHP来执行
iis 6.0 解析漏洞
例子:webshell.asp;.jpg会被解析成webshell.asp
iis 7.0/iis7.5/Nginx<=0.8.37 解析漏洞
原理:当PHP中的cgi.fix_pathinfo开启时,PHP就可以对url进行"修理"
例子:假如遇到1.jpg/2.txt/3.PHP这个url时,因为不存在3.PHP这个东西,所以会继续往前寻找,结果2.txt也不存在,就再往前找,发现1.jpg存在,就把1.jpg里面的内容当作PHP解析了
RTE富文本漏洞 功能漏洞
攻击手法总结
- 添加文件头幻数
- 更改文件mime类型
- windows特性绕过
- 更改文件拓展名
- 解析漏洞
- 0x00截断
- 制作图片马
- 更改前端JS脚本
- 上传重写" .htaccess "配置文件
- RTE软件漏洞
- 依靠版本漏洞