File Upload(文件上传)
File Upload(文件上传),由于对上传文件的内、类型没有做严格的过滤、检查,使得攻击者可以通过上传木马文件获取服务器的webshell文件。
low
这关需要我们上传一个图片文件,但我们明确了文件上传漏洞的定义,知道他是由于没有严格检查文件内容
因此我们试着上传一个并非图片的文件,如下(用于显示php的配置信息)
可以看到上传成功了,并给我们指出了路径,试着访问一下
确实给我们返回了php的配置文件
源码分析
<?php if( isset( $_POST[ 'Upload' ] ) ) { // Where are we going to be writing to? //文件的目标路径hackable/uploads/,也就是文件上传的位置 $target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/"; // basename(path,suffix) //函数返回路径中的文件名部分,如果可选参数suffix为空,则返回的文件名包含后缀名,反之不包含后缀名。 $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>"; } } ?>从源码中可以看到对上传文件的类型、内容没有做任何的过滤与检查,同时告诉了我们文件上传的路径,存在明显的文件上传漏洞。
Medium
我们还是先试试上传一个php文件
失败了,告诉我们只能上传JPEG或者PNG的图片文件
那我们抓个包看看
可以看到我圈出了Content-Type这个元素,它用于告诉服务器:"我这个请求体里的数据是什么格式"。
常见文件对应的 Content-Type
文件类型 | Content-Type 值 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
回到原题中,要求我们要上传图片格式,那我们就改变其content-type值(改为所需要的图片类型值)再上传试试
能看到上传成功了
源码分析
<?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? //文件类型必须是image/jpeg 或者 image/png,大小不能超过100000B(约为97.6KB) 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>'; } } ?>对文件上传的类型做了限制,要求必须是image/jpeg 或者 image/png 类型的,但我们依旧可以抓包改包绕过
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? // strtoLower把所有字符转换为小写 getimagesize(string filename) 函数会通过读取文件头,返回图片的长、宽等信息,如果没有相关的图片文件头,函数会报错。 可以看到,High级别的代码读取文件名中最后一个”.”后的字符串,期望通过文件名来限制文件类型,因此要求上传文件名形式必须是”*.jpg”、”*.jpeg” 、”*.png”之一。同时,getimagesize函数更是限制了上传文件的文件头必须为图像类型。 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>'; } } ?>这里用到了getimagesize($uploaded_tmp)这个函数,强制要求上传图片
我们可以制作一个图片马
首先需要准备一个图片,一个脚本文件(我用的还是最简单的读取php配置文件)
在命令行里,使用copy合并两个文件(这是制作图片马最常用的方式)
这里需要说明一下
/b表示以二进制方式复制(图片文件用二进制)/a表示以ASCII方式复制(PHP文件用文本)image_shell.png是最终生成的文件名
合并后大家可以看到新文件大小应该 ≈phpinfo.png+phpinfo.php的大小之和
并且我们用记事本打开这个图片文件,可以在末尾看到这样的字符,这说明我们的图片马制作成功了
有关其他方式制作图片马可以参考大佬写的这篇文章做一个图片马(图片木马)的四种方法 小白也能看会(详细步骤 ) 需要.htaccess等执行图片内代码_图片马制作-CSDN博客
然后选择一手上传,成功了,还告诉了我们文件路径
接下来我们回到文件包含漏洞中验证一下,在URL中输入刚刚上传成功的图片马路径,回车
页面顶部出现的乱码,是图片马中PNG图片的二进制数据被浏览器/服务器误当作文本字符进行渲染时产生的。这并不影响其后的PHP代码被成功执行,也恰好证明了该图片马已通过了getimagesize()的图片头检测,成功给我们返回了php配置信息
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 ) . '-'; //上传文件的文件前缀md5加密 $target_file = md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext; //in_get(varname) 函数返回相应选项的值 $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' ) { //imagecreatefromjpeg ( filename ) 函数返回图片文件的图像标识,失败返回false $img = imagecreatefromjpeg( $uploaded_tmp ); //imagejpeg ( image , filename , quality) 从image图像以filename为文件名创建一个JPEG图像,可选参数quality,范围从0(最差质量,文件更小)到100(最佳质量,文件最大)。 imagejpeg( $img, $temp_file, 100); } else { $img = imagecreatefrompng( $uploaded_tmp ); imagepng( $img, $temp_file, 9); } // imagedestroy( img ) 函数销毁图像资源 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(); ?>这个级别的文件上传对上传的文件进行了重命名(搞了一个MD5的加密),还增加了token值的校验,对文件的内容也做了严格的检查。