DVWA-5.4 File Upload(文件上传)-Impossible
Impossible Level
查看源码
<?php if( isset( $_POST[ 'Upload' ] ) ) { // Check Anti-CSRF token----校验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;//重写文件名(在文件名前面加id再整体md5) $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?
// 校验文件类型及大小
// 文件名后缀必须为jpg、jpeg、png之一,
// 并且文件大小必须小于100000B约为97.7KB,
// 并且文件类型必须为image/jpeg或image/png
// 并且可以使用getimagesize()返回图像的大小和类型(注意,这个函数本身是不安全的) 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! $html .= "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>"; } else { // No $html .= '<pre>Your image was not uploaded.</pre>'; } // Delete any temp files if( file_exists( $temp_file ) ) unlink( $temp_file ); } else { // Invalid file $html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>'; } } // Generate Anti-CSRF token generateSessionToken(); ?>
相关函数介绍
substr(string,start,length)
函数返回字符串的一部分
strrpos(string,find,start)
函数查找字符串在另一字符串中最后一次出现的位置(区分大小写)
uniqid()
函数基于以微秒计的当前时间,生成一个唯一的 ID
in_get(varname)
函数返回相应选项的值
imagecreatefromjpeg ( filename )
函数返回图片文件的图像标识,失败返回false
imagejpeg ( image , filename , quality)
从image图像以filename为文件名创建一个JPEG图像,可选参数quality,范围从 0(最差质量,文件更小)到 100(最佳质量,文件最大)。
imagedestroy( img )
函数销毁图像资源
getimagesize()
函数用来获取图像的大小和类型
可以看到,Impossible级别的代码对上传文件进行了重命名(为md5值,导致%00截断无法绕过过滤规则),加入Anti-CSRF token防护CSRF攻击,同时对文件的内容作了严格的检查,导致攻击者无法上传含有恶意脚本的文件。
扩展
源码中使用了一个非常不安全的的函数:getimagesize()
getimagesize()函数会对目标文件的16进制去进行一个读取,去读取头几个字符串是不是符合图片的要求的。
getimagesize()返回结果中有文件大小和文件类型,如果用这个函数来获取类型,从而判断是否是图片的话,会存在问题。因为图片头可以被伪造,我们完全可以通过伪造正确的图片头来绕过它对图片类型的检查。
这就是图片的十六进制,前几位都是一样的
按照这样的逻辑,我们就可以去伪造一个假图片,让函数以为我们这就是图片,达到绕过的目的。
- 方法1 直接伪造头部GIF89A
- 方法2 CMD:copy /b test.png+munma.php hack.png
- 方法3 使用GIMP(开源的图片修改软件),通过增加备注,写入执行命令。
但是,即使我们可以根据上述方法绕过函数getimagesize(),我们也绕不过imagecreatefromjpeg ( filename )、imagejpeg ( image , filename , quality)、imagedestroy( img )等几个函数对我们上传图片的重塑。所以,Impossible等级的代码是比较安全的。
参考:
https://www.freebuf.com/articles/web/119467.html
https://blog.csdn.net/weixin_43915842/article/details/90183305