这里我主要是先通过讲解的内容再利用dvwa进行演示。
漏洞原理以及危害:
大部分的网站和应用系统都有上传功能,一些文件上传功能实现代码没有严格限制用户上传的文件后缀以及文件类型,导致允许攻击者向某个可通过Web访问的目录上传任意PHP文件,并能够将这些文件传递给PHP解释器,就可以在远程服务器上执行任意PHP脚本。
文件上传漏洞本身就是一个危害巨大的漏洞,WebShell更是将这种漏洞的利用无限扩大。大多数的上传漏洞被利用后攻击者都会留下WebShell以方便后续进入系统。攻击者在受影响系统放置或者插入WebShell后,可通过该WebShell更轻松,更隐蔽的在服务中为所欲为。
漏洞的利用:
当系统存在文件上传漏洞时攻击者可以将病毒,木马,WebShell,其他恶意脚本或者是包含了脚本的图片上传到服务器,这些文件将对攻击者后续攻击提供便利。根据具体漏洞的差异,此处上传的脚本可以是正常后缀的PHP,ASP以及JSP脚本,也可以是篡改后缀后的这几类脚本。
上传文件是病毒或者木马时,主要用于诱骗用户或者管理员下载执行或者直接自动运行;
上传文件是WebShell时,攻击者可通过这些网页后门执行命令并控制服务器;
上传文件是其他恶意脚本时,攻击者可直接执行脚本进行攻击;
上传文件是恶意图片时,图片中可能包含了脚本,加载或者点击这些图片时脚本会悄无声息的执行;
上传文件是伪装成正常后缀的恶意脚本时,攻击者可借助本地文件包含漏洞(Local File Include)执行该文件。如将bad.php文件改名为bad.doc上传到服务器,再通过PHP的include,include_once,require,require_once等函数包含执行。
此处造成恶意上传的有三种原因:
1)文件上传时检查不严。没有进行文件格式检查。一些应用仅仅在客户端进行了检查,而在专业的攻击者眼里几乎所有的客户端检查都等于没有检查,攻击者可以通过NC,Fiddler等断点上传工具轻松绕过客户端的检查。一些应用虽然在服务器端进行了黑名单检查,但是却可能忽略了大小写,如将.php改为.Php即可绕过检查;一些应用虽然在服务器端进行了白名单检查却忽略了%00截断符,如应用本来只允许上传jpg图片,那么可以构造文件名为xxx.php%00.jpg,其中%00为十六进制的0x00字符,.jpg骗过了应用的上传文件类型检测,但对于服务器来说,因为%00字符截断的关系,最终上传的文件变成了xxx.php。
2)文件上传后修改文件名时处理不当。一些应用在服务器端进行了完整的黑名单和白名单过滤,在修改已上传文件文件名时却百密一疏,允许用户修改文件后缀。如应用只能上传.doc文件时攻击者可以先将.php文件后缀修改为.doc,成功上传后在修改文件名时将后缀改回.php。
3)使用第三方插件时引入。好多应用都引用了带有文件上传功能的第三方插件,这些插件的文件上传功能实现上可能有漏洞,攻击者可通过这些漏洞进行文件上传攻击。如著名的博客平台WordPress就有丰富的插件,而这些插件中每年都会被挖掘出大量的文件上传漏洞。
绕过方法:
前端js检测:
绕过方式:在前端页面修改js或者删除当前js直接上传,抓包工具拦截后修改后缀名上传
MIME检测:
这里的MIME其实就是对上传文件的Content-Type进行了检测
绕过方式:在上传文件时,开启Burp抓包工具,修改Content-Type为合法上传文件类型,比如:img/gif
文件后缀名黑名单检测:
代码的意思就是将不允许上传的后缀存到一个数组中,提前上传文件的后缀,在数组中进行对比,如果存在即上传失败,如果不存在即上传的文件后缀合法。
绕过方式:
asp:asp|asa|cer|cdx|aspx|ascx|asax|asmx|cfc|cfm
php:php|php2|php3|php4|php5|pthml|phtm|
jsp:jsp|jspa|jspx|jsw|jsv|jspf|jtml|
后缀名大小写绕过:
PHP
ASP
Php
利用windows特征绕过:
shell.php.
shell.php(空格)
shell.php:1.jpg
shell.php::
D
A
T
A
(
N
T
F
S
特
性
流
)
s
h
e
l
l
.
p
h
p
:
:
DATA(NTFS特性流) shell.php::
DATA(NTFS特性流)shell.php::DATA…
以上后缀,会被windows系统自动去掉不符合规则符号后面的内容。
将非法的后缀过滤为空:
绕过方式:后缀双写绕过,如:pphphp即可绕过。
.htaccess绕过:
.htaccess是apache服务器中的一个配置文件,它负责相关目录下的网页配置,通过.htaccess文件可以帮我们实现网页301重定向(永久),自定义404错误页面,改变文件拓展名、允许/组织特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。
如果用户可以自定义上传.htaccess文件,那么在文件内容中定义"AddType application/x-httpd-php .jpg"那么当前目录下的所有.jpg后缀的文件都会以php来解析。
.user.ini绕过:
利用方式:
前提是所上传文件夹下有正常的php文件,随后上传.user.ini文件,内容为:auto_prepend_fifile=01.gif,上传木马<?php @eval(REQUEST[shell]);?>,修改后缀名为01.gif访问同目录下的php文件,包含上传的木马进行解析http://www.test.com/echo.phpshell=phpinfo();
00截断绕过:
Chr(chracode)返回与指定的ASCII字符代码相对应的字符从0到31的数字表示标准的不可打印的ASCII代码,通过ASCII码得知0=null chr(0)表示字符串结束。
而00截断常见的有%00、0x00等。
他们都表示ASCII字符表中的保留字符chr(0)。不管表示编码有什么区别,只要能让服务器正确解析为chr(0)就行。
原理:chr(0)表示结束,限制条件:小于php5.3.4 小于jdk1.7,magic_quoes_gpc=off
在上传文件的时候,如果上传的路径可控,那么在路径后加入/1.php%00当文件系统读到[0x00]时,会认为文件已经结束。
这里需要注意的是%00是添加在URL中的,所以直接添加即可。
0x00绕过:
这里的0x00和上面的%00绕过的原理时类似的,都是在文件系统读取到[0x00]时,会认为文件已经结束。利用方式如下:
在这里路径的位置后面添加1.php a只是一般空格的16进制为0x20,添加一个a是为了找到空格的位置,算是起到一个标记符的作用,此时通过Burp的Hex查找空格对饮的16进制位置,修改为00
此时点击Send,即可上传成功。
对WAF的一些绕过姿势
1:安全狗绕过
绕过思路:
对文件的内容,数据,数据包进行处理。Content-Disposition: form-data; name=“upload_file”; filename=“info.php” 将form-data;修改为~form-data;
通过大小写来进行绕过:
Content-Disposition:form-data;name=“upload_file”; file=“info.php” Content-Type: application/octet-stream
将Content-Disposition修改为content-disposition。
将form-data修改为Form-data。
将Content-Type修改为content-Type。
通过删减空格来进行绕过:
Content-Disposition:form-data;name=“upload_file”; file=“info.php” Content-Type: application/octet-stream
将Content-Disposition:form-data 冒号后面增加或者减少一个空格。
将form-data;name="upload_file;分号后面增加或减少一个空格。
将Content-Type:冒号后面增加一个空格。
通过字符串拼接绕过:
Content-Disposition:form-data;name=“upload_file”; file=“info.php”
将form-data修改为f+orm-data。
将form-data修改为form-d+ata。
双文件上传绕过:
HTTP header 属性值绕过:
Content-Disposition:form-data;name=“upload_file”; file=“info.php”
将form-data替换为来绕过
Content-Disposition:;name=“upload_file”; file=“info.php”
HTTP header 属性名称绕过:
Content-Disposition: form-data; name=“image”; filename="085733uykwusqcs8vw8wky.png"Content-Type: image/png
修改为:
Content-Disposition: form-data; name=“image”; filename=“085733uykwusqcs8vw8wky.png C.php”
删除掉ontent-Type:image/jpeg只留下c,将.php加到c后面即可,但是要注意,双引号要跟着c.php
等效替换绕过:
原内容:
Content-Type: multipart/form-data; boundary=---------------------------471463142114
修改为:
Content‐Type: multipart/form‐data; boundary =‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐471463142114
bodundary后面加入空格。
2:WTS-WAF绕过:
原内容:
Content-Disposition: form-data; name=“up_picture”; filename=“xss.php”
添加回车来进行绕过。
3:百度云上传绕过
直接大小写修改文件名即可绕过。
Content-Disposition: form-data; name=“up_picture”; filename=“xss.Php”
4:360主机上传绕过
原内容:
Content-Disposition: form-data; name=“image”; filename="085733uykwusqcs8vw8wky.png"Content- Type: image/png
修改为:
将Content-Disposition修改为Content‐空格Disposition皆可绕过。
5:CONTENT-LENGTH绕过
针对这种类型的验证,我们可以通过上传一些非常短的恶意代码来绕过。上传文件的大小取决于,Web服务器上的最大 长度限制。我们可以使用不同大小的文件来fuzzing上传程序,从而计算出它的限制范围。
6:文件内容检测绕过
针对文件内容检测的绕过,一般有两种方式:
制作图片马
文件幻术头绕过,垃圾数据填充绕过,修改http请求,再之加入大量垃圾数据。
防御:
首先,上传的文件能够被Web容器解释执行。所以文件上传后所在的目录要是Web容器所覆盖到的路径。
其次,用户能够从Web上访问这个文件。如果文件上传了,但用户无法通过Web访问,或者无法得到Web容器解释这个脚本,那么也不能称之为漏洞。
最后,用户上传的文件若被安全检查、格式化、图片压缩等功能改变了内容,则也可能导致攻击不成功。
防范文件上传漏洞常见的几种方法。
1、文件上传的目录设置为不可执行
只要web容器无法解析该目录下面的文件,即使攻击者上传了脚本文件,服务器本身也不会受到影响,因此这一点至关重要。
2、判断文件类型
在判断文件类型时,可以结合使用MIME Type、后缀检查等方式。在文件类型检查中,强烈推荐白名单方式,黑名单的方式已经无数次被证明是不可靠的。此外,对于图片的处理,可以使用压缩函数或者resize函数,在处理图片的同时破坏图片中可能包含的HTML代码。
3、使用随机数改写文件名和文件路径
文件上传如果要执行代码,则需要用户能够访问到这个文件。在某些环境中,用户能上传,但不能访问。如果应用了随机数改写了文件名和路径,将极大地增加攻击的成本。再来就是像shell.php.rar.rar和crossdomain.xml这种文件,都将因为重命名而无法攻击。
4、单独设置文件服务器的域名
由于浏览器同源策略的关系,一系列客户端攻击将失效,比如上传crossdomain.xml、上传包含Javascript的XSS利用等问题将得到解决。
l 系统开发阶段的防御
系统开发人员应有较强的安全意识,尤其是采用PHP语言开发系统。在系统开发阶段应充分考虑系统的安全性。对文件上传漏洞来说,最好能在客户端和服务器端对用户上传的文件名和文件路径等项目分别进行严格的检查。客户端的检查虽然对技术较好的攻击者来说可以借助工具绕过,但是这也可以阻挡一些基本的试探。服务器端的检查最好使用白名单过滤的方法,这样能防止大小写等方式的绕过,同时还需对%00截断符进行检测,对HTTP包头的content-type也和上传文件的大小也需要进行检查。
l 系统运行阶段的防御
系统上线后运维人员应有较强的安全意思,积极使用多个安全检测工具对系统进行安全扫描,及时发现潜在漏洞并修复。定时查看系统日志,web服务器日志以发现入侵痕迹。定时关注系统所使用到的第三方插件的更新情况,如有新版本发布建议及时更新,如果第三方插件被爆有安全漏洞更应立即进行修补。对于整个网站都是使用的开源代码或者使用网上的框架搭建的网站来说,尤其要注意漏洞的自查和软件版本及补丁的更新,上传功能非必选可以直接删除。除对系统自生的维护外,服务器应进行合理配置,非必选一般的目录都应去掉执行权限,上传目录可配置为只读。
l 安全设备的防御
文件上传攻击的本质就是将恶意文件或者脚本上传到服务器,专业的安全设备防御此类漏洞主要是通过对漏洞的上传利用行为和恶意文件的上传过程进行检测。恶意文件千变万化,隐藏手法也不断推陈出新,对普通的系统管理员来说可以通过部署安全设备来帮助防御。目前华三通信公司发布的SecPath IPS系列产品经过长期的积累,不但可以基于行为对网络中大量文件上传漏洞的利用进行检测,同时还能基于内容对恶意文件进行识别。
DVWA实例:
主要是以dvwa中的实例为主:
security:low
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to? 我们要写信去哪里?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
// Can we move the file to the upload folder? 我们能把文件移到上传文件夹吗?
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
// No
$html .= '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
$html .= "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
?>
代码解析:
isset()函数用于检测变量是否已设置并且非NULL,若使用isset()测试一个被设置成NULL的变量。将返回FALSE,同时要注意的是null字符(”\0“)并不等同于PHP的NULL常量。
PHP版本要求:PHP 4, PHP 5,PHP 7 返回值: 如果指定的变量存在且不为null,则返回TRUE,否者返回FALSE.
$_POST 变量 预定义的 $_POST 变量用于收集来自 method=“post” 的表单中的值。
从带有 POST 方法的表单发送的信息,对任何人都是不可见的(不会显示在浏览器的地址栏),并且对发送信息的量也没有限制。
注释:然而,默认情况下,POST 方法的发送信息的量最大值为 8 MB(可通过设置 php.ini 文件中的 post_max_size进行更改)
basename()函数返回路径中的文件名部分。
basename(path,suffix)
参数 | 描述 |
---|---|
path | 必须。规定要检查的路径 。 |
name | 可选。规定文件扩展名。如果文件有名有文件扩展名,将不会显示这个扩展名。 |
move_uploaded_file() 函数把上传的文件移动到新位置。
如果成功该函数返回 TRUE,如果失败则返回 FALSE。
move_uploaded_file(file,newloc)
参数 | 描述 |
---|---|
file | 必须。规定要移动的文件 |
newloc | 必须。规定文件的新位置 |
现在我们对这段代码有了大致的了解:就是先判断你的这个upload的文件是否是null,不是则进行下一步,定义了上传的路径,如果你上传的文件在这个路径中,则代表上传成功,反之亦然。
这段代码也没有什么可以过滤的东西,就先上传一句话木马看看效果。
嘿嘿黑,我和我的小伙伴们,这不上传成功了?!用蚁剑链接一下看看。
嘿呦,这不成功了吗?
security:medium
这时我们看了看,好像不行唉,宝~该怎么办?!
Your image was not uploaded. We can only accept JPEG or PNG images.
您的图像未上载。我们只能接受JPEG或PNG图像。
现在需要另一个利器了bp,嘿。。。
改一下,把文件从这个php,改成jpeg或者png.
同时你也可以利用%00来阻断jpg.
security:high
感觉又不行啦?!
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];
// Is it an image?
if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
( $uploaded_size < 100000 ) &&
getimagesize( $uploaded_tmp ) ) {
// Can we move the file to the upload folder? 我们能把文件移到上传文件夹吗?
if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) {
// No
$html .= '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
$html .= "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
$html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
?>
strrpos(string,find,start)
函数返回字符串find在另一字符串string中最后一次出现的位置,如果没有找到字符串则返回false,可选参数start规定在何处开始搜索。
getimagesize(string filename)
函数会通过读取文件头,返回图片的长、宽等信息,如果没有相关的图片文件头,函数会报错。
可以看到,High级别的代码读取文件名中最后一个”.”后的字符串,期望通过文件名来限制文件类型,因此要求上传文件名形式必须是”.jpg”、”.jpeg”
、”*.png”之一。同时,getimagesize函数更是限制了上传文件的文件头必须为图像类型。
利用另外一个神器:winhex
放入一句话木马。
在此要说明一下,这个图片是要又大小的要求的。
security:impossible
这次是它虽然是上传成功了,但是并没有显示他的位置在哪里?!
这个问题,也不是很简单?!好像是无解的?!研究吧?!
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];
// 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;
// Is it an image?
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 ) ) {
// Strip any metadata, by re-encoding image (Note, using php-Imagick is recommended over php-GD) 通过重新编码图像来去除任何元数据(注意,建议使用php Imagick而不是php GD)
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 );
// Can we move the file to the web root from the temp folder? 我们可以将文件从临时文件夹移动到web根目录吗
if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) {
// Yes!
echo "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>";
}
else {
// No
echo '<pre>Your image was not uploaded.</pre>';
}
// Delete any temp files
if( file_exists( $temp_file ) )
unlink( $temp_file );
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
imagecreatefromjpeg(filename):从给定的文件或url中创建一个新的图片
imagejpeg(image,filename,quality):从image图像中以 filename
文件名创建一个jpeg的图片,参数quality可选,0-100 (质量从小到大)
imagedestroy(image) : 销毁图像
可以看到,Impossible级别对上传的文件进行了重命名(为md5值,导致00截断无法绕过过滤规则),并且加入Anti-CSRF
token防护CSRF攻击,同时对文件的内容作了严格的检查,导致攻击者无法上传含有恶意脚本的文件。
总结
基本上如何绕过这些php中的这些函数,先把上传的代码搞懂,在进行上传会有很深的体会与理解。
一个人光溜溜的到这个世界来,最后光溜溜的离开这个世界而去,彻底想起来,名利都是身外物,只有尽一人的心力,使社会上的人多得他工作的裨益,是人生最愉快的事情。——邹韬奋