soarli

通过PHP实现安全上传文件
6.15下午手动编写了一个文件接收程序原始代码为保存,以下为修改后的漏洞等效代码:index.html:<!...
扫描右侧二维码阅读全文
16
2021/06

通过PHP实现安全上传文件

6.15下午手动编写了一个文件接收程序

原始代码为保存,以下为修改后的漏洞等效代码:

index.html

<!DOCTYPE html>
<html>
<head>
    <title>上传CSS入口</title>
    <meta charset="utf-8">
</head>
<body>
<form enctype="multipart/form-data" action="upload.php" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="2097152"><!--优先通过浏览器引导用户上传指定大小文件,为后端检测提供第一道防线,但是本行可删,2*1024*1024-->
    上传CSS文件(不得超过2M):<input type="file" id='myfile' name="userfile" accept="text/css" onchange="fileChange(this);"><!--优先通过浏览器善意引导用户上传指定类型的文件,但是本行可删,用户也可在浏览器中选择“所有文件”-->
    <input type="submit" id="btn" value="上传">
<!--以下代码优先通过浏览器善意引导用户上传指定大小的文件同时节省不必要的带宽浪费,但是JS可禁用-->
<!-- <script type="text/javascript">
    document.getElementById("btn").onclick=function(){
        if (document.getElementById("myfile").files[0].size > 2097152) {alert('请上传小于2MB的文件!(由前端JS发现)');return false;}
    };</script> -->

 <!--以下代码优先通过浏览器善意引导用户上传指定类型和大小的文件同时节省不必要的带宽浪费,但是JS可禁用-->
  <script type="text/javascript">
    var isIE = /msie/i.test(navigator.userAgent) && !window.opera;

    function fileChange(target, id) {
      var fileSize = 0;
      var filetypes = [".css"];
      var filepath = target.value;
      var filemaxsize = 1024 * 2;//2M
      if (filepath) {
        var isnext = false;
        var fileend = filepath.substring(filepath.indexOf("."));
        if (filetypes && filetypes.length > 0) {
          for (var i = 0; i < filetypes.length; i++) {
            if (filetypes[i] == fileend) {
              isnext = true;
              break;
            }
          }
        }
        if (!isnext) {
          alert("上传失败,请选择css文件上传。(由前端发现)");
          target.value = "";
          return false;
        }
      } else {
        return false;
      }
      if (isIE && !target.files) {
        var filePath = target.value;
        var fileSystem = new ActiveXObject("Scripting.FileSystemObject");
        if (!fileSystem.FileExists(filePath)) {
          alert("上传失败,附件不存在,请重新选择(由前端发现)!");
          return false;
        }
        var file = fileSystem.GetFile(filePath);
        fileSize = file.Size;
      } else {
        fileSize = target.files[0].size;
      }

      var size = fileSize / 1024;
      if (size > filemaxsize) {
        alert("上传失败,请上传小于" + filemaxsize / 1024 + "MB的文件!(由前端发现)!");
        target.value = "";
        return false;
      }
      if (size <= 0) {
        alert("上传失败,附件大小不能为0M(由前端发现)!");
        target.value = "";
        return false;
      }
    }
  </script>
</form>
</body>
</html>

upload.php

<?php


// 后端验证防止上传超过2M的文件,定义最大值常量
define('MAX_SIZE',2097152);
define('MuLu','css/');

// echo $_FILES;  返回Array
// print_r($_FILES);   // 返回这种结构:
/*

Array
(
    [userfile] => Array
        (
            [name] => 1.jpg
            [type] => image/jpeg
            [tmp_name] => C:\Users\soarli\AppData\Local\Temp\php5449.tmp
            [error] => 0
            [size] => 376151
        )

)

*/

// 只允许上传jpg格式的图片
// if ($_FILES['userfile']['type'] != 'image/jpeg' && $_FILES['userfile']['type'] != 'image/png' && $_FILES['userfile']['type'] != 'image/gif') {
//     echo "<script>alert('本站只允许上传jpg/png/gif格式的图片,请重新选择!');history.back();</script>";
//     exit;
// }

// 使用以下方法设置格式白名单更加直观
// switch ($_FILES['userfile']['type']){
//     case 'image/jpeg':
//         break;
//     case 'image/png':
//         break;
//     case 'image/gif':
//         break;
//     default: echo "<script>alert('本站只允许上传jpg/png/gif格式的图片,请重新选择!');history.back();</script>";
//     exit;
// }


// 如果上传失败,根据$_FILES的error值判断报错类型并在前端提示
if ($_FILES['userfile']['error'] > 0) {
    switch ($_FILES['userfile']['error']) {
        case 1: echo "<script>alert('上传失败,上传文件超过PHP配置文件限定值大小(后端的限制,由后端发现)!');history.back();</script>上传失败,上传文件超过PHP配置文件限定值大小(后端的限制,由后端发现)!";
            break;
        case 2: echo "<script>alert('上传失败,上传文件超过HTML表单限定值大小(前端的限制,由后端发现)!');history.back();</script>上传失败,上传文件超过HTML表单限定值大小(前端的限制,由后端发现)!";
            break;
        case 3: echo "<script>alert('上传失败(部分被上传)!');history.back();</script>上传失败(部分被上传)!";
            break;    
        case 4: echo "<script>alert('上传失败,没有任何部分被上传!');history.back();</script>上传失败,没有任何部分被上传!";
            break;    
    }
    exit;
}

// 后端判断上传文件的大小
if($_FILES['userfile']['size'] > MAX_SIZE){
    echo "<script>alert('上传失败,上传文件超过限定值大小(由后端大小检测模块发现)!');history.back();</script>上传失败,上传文件超过限定值大小(由后端大小检测模块发现)!";
    exit;
}

// 只允许上传图片
// $file_Extension_string = strrev($_FILES['userfile']['name']); // 从$_FILES数组中获取name字段并反转字符串
// $file_Extension_array = explode('.',$file_Extension_string); // 以.号为分割拆分成为新的数组
// $houZhui = strrev($file_Extension_array[0]); // 反转反转过的扩展名字符串存为$houZhui
// 以下两行代表的两种方法在实际应用中仅保留第二种即可(第一种抓包篡改Content-Type即可绕过,第二种即便抓包篡改Content-Disposition绕过检测也无法执行后续恶意操作‘后缀名都变了’)
$fileMimes = array('text/css'); //允许上传的文件类型,实际应用时记得写前面(方便维护人员调试)
// $accepted_houzhui = array('css'); //允许上传的文件扩展名类型,实际应用时记得写前面(方便维护人员调试)
// 判断类型/后缀是否同时分别包含在两个数组
if (is_array($fileMimes)) {
    if (!in_array($_FILES['userfile']['type'],$fileMimes)) {
        echo "<script>alert('上传失败,请选择css文件上传。(由后端类型检测模块发现)');history.back();</script>上传失败,请选择css文件上传。(由后端类型检测模块发现)";
        exit;
    }
}

// 判断目录是否存在,若无则创建
if(!is_dir(MuLu)){
    mkdir(MuLu,0777);
}

// 以上模块均没异常,开始将接收到的临时文件按照本规则重命名并移动到指定目录
if (is_uploaded_file($_FILES['userfile']['tmp_name'])) {

    if(!move_uploaded_file($_FILES['userfile']['tmp_name'],MuLu.$_FILES['userfile']['name'])){
        echo "<script>alert('【后端错误】文件移动失败!');history.back();</script>【后端错误】文件移动失败!";
        exit;
    }
    
} else {
    echo "<script>alert('【后端错误】临时文件夹找不到上传的文件!');history.back();</script>【后端错误】临时文件夹找不到上传的文件!";
    exit;
}

// 以上都正常执行,则认为文件上传成功,并显示上传成功的提示
$success = 'http://172.28.21.166:20020/upload/css/'.$_FILES['userfile']['name'];
echo " <script>    function CopyToClipboard(text) {
    var input = document.createElement(\"input\");
    var currentFocus = document.activeElement;
    document.body.appendChild(input);
    input.readOnly = 'readonly';
    input.value = text;
    input.focus();
    if (input.setSelectionRange)
      input.setSelectionRange(0, input.value.length);
    else
      input.select();
    try {
      var flag = document.execCommand(\"copy\");
    } catch (eo) {
      var flag = false;
    }
    input.blur();
    document.body.removeChild(input);
    currentFocus.focus();
    currentFocus.blur();
    if (flag){
        alert('复制成功!');
        location.href='index.html';
    } else{
        alert('复制失败');
        location.href='index.html';
    }
    return flag;
  }
   </script>";

echo "文件上传成功,地址如下:<br>";
echo $success;
echo "<br><br><a href=\"javascript:location.href='index.html';\">直接返回</a>&nbsp;&nbsp;&nbsp;<a href=\"javascript:CopyToClipboard('$success');\">复制并返回</a>&nbsp;&nbsp;&nbsp;<a href=\"$success\" target=\"_blank\">点击查看</a><style type=\"text/css\">a{text-decoration: none;color: blue;font-size:16px;}</style>";
// echo "<script>alert('文件上传成功!');location.href='index.html';</script>文件上传成功!";

// 图片上传成功的提示,随即显示图片预览
// echo "<script>alert('文件上传成功!');location.href='upload_view.php?url=".htmlspecialchars($_FILES['userfile']['name'])."';</script>";

?>

6.16通过验证后缀实现安全上传

upload.html

<!DOCTYPE html>
<html>
<head>
    <title>上传文件</title>
    <meta charset="utf-8">
</head>
<body>
<form enctype="multipart/form-data" action="upload.php" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="2097152"><!--优先通过浏览器引导用户上传指定大小文件,为后端检测提供第一道防线,但是本行可删,2*1024*1024-->
    上传文件(不得超过2M):<input type="file" id='myfile' name="userfile" accept="image/jpeg,image/jpg,image/png,image/gif" onchange="fileChange(this);"><!--优先通过浏览器善意引导用户上传指定类型的文件,但是本行可删,用户也可在浏览器中选择“所有文件”-->
    <input type="submit" id="btn" value="上传">
<!--以下代码优先通过浏览器善意引导用户上传指定大小的文件同时节省不必要的带宽浪费,但是JS可禁用-->
<!-- <script type="text/javascript">
    document.getElementById("btn").onclick=function(){
        if (document.getElementById("myfile").files[0].size > 2097152) {alert('请上传小于2MB的文件!(由前端JS发现)');return false;}
    };</script> -->

 <!--以下代码优先通过浏览器善意引导用户上传指定类型和大小的文件同时节省不必要的带宽浪费,但是JS可禁用-->
  <script type="text/javascript">
    var isIE = /msie/i.test(navigator.userAgent) && !window.opera;

    function fileChange(target, id) {
      var fileSize = 0;
      var filetypes = [".jpg", ".png", ".gif", ".jpeg"];
      var filepath = target.value;
      var filemaxsize = 1024 * 2;//2M
      if (filepath) {
        var isnext = false;
        var fileend = filepath.substring(filepath.indexOf("."));
        if (filetypes && filetypes.length > 0) {
          for (var i = 0; i < filetypes.length; i++) {
            if (filetypes[i] == fileend) {
              isnext = true;
              break;
            }
          }
        }
        if (!isnext) {
          alert("上传失败,本站只允许上传jpg/png/gif格式的图片,请重新选择!(由前端JS发现)");
          target.value = "";
          return false;
        }
      } else {
        return false;
      }
      if (isIE && !target.files) {
        var filePath = target.value;
        var fileSystem = new ActiveXObject("Scripting.FileSystemObject");
        if (!fileSystem.FileExists(filePath)) {
          alert("上传失败,附件不存在,请重新选择(由前端JS发现)!");
          return false;
        }
        var file = fileSystem.GetFile(filePath);
        fileSize = file.Size;
      } else {
        fileSize = target.files[0].size;
      }

      var size = fileSize / 1024;
      if (size > filemaxsize) {
        alert("上传失败,请上传小于" + filemaxsize / 1024 + "MB的文件!(由前端JS发现)!");
        target.value = "";
        return false;
      }
      if (size <= 0) {
        alert("上传失败,附件大小不能为0M(由前端JS发现)!");
        target.value = "";
        return false;
      }
    }
  </script>
</form>
</body>
</html>

upload.php

<?php


// 后端验证防止上传超过2M的文件,定义最大值常量
define('MAX_SIZE',2097152);
define('MuLu','upload_heidong/');

// echo $_FILES;  返回Array
// print_r($_FILES);   // 返回这种结构:
/*

Array
(
    [userfile] => Array
        (
            [name] => 1.jpg
            [type] => image/jpeg
            [tmp_name] => C:\Users\soarli\AppData\Local\Temp\php5449.tmp
            [error] => 0
            [size] => 376151
        )

)

*/

// 只允许上传jpg格式的图片
// if ($_FILES['userfile']['type'] != 'image/jpeg' && $_FILES['userfile']['type'] != 'image/png' && $_FILES['userfile']['type'] != 'image/gif') {
//     echo "<script>alert('本站只允许上传jpg/png/gif格式的图片,请重新选择!');history.back();</script>";
//     exit;
// }

// 使用以下方法设置格式白名单更加直观
// switch ($_FILES['userfile']['type']){
//     case 'image/jpeg':
//         break;
//     case 'image/png':
//         break;
//     case 'image/gif':
//         break;
//     default: echo "<script>alert('本站只允许上传jpg/png/gif格式的图片,请重新选择!');history.back();</script>";
//     exit;
// }


// 如果上传失败,根据$_FILES的error值判断报错类型并在前端提示
if ($_FILES['userfile']['error'] > 0) {
    switch ($_FILES['userfile']['error']) {
        case 1: echo "<script>alert('上传失败,上传文件超过PHP配置文件限定值大小(后端的限制,由后端发现)!');history.back();</script>上传失败,上传文件超过PHP配置文件限定值大小(后端的限制,由后端发现)!";
            break;
        case 2: echo "<script>alert('上传失败,上传文件超过HTML表单限定值大小(前端的限制,由后端发现)!');history.back();</script>上传失败,上传文件超过HTML表单限定值大小(前端的限制,由后端发现)!";
            break;
        case 3: echo "<script>alert('上传失败(部分被上传)!');history.back();</script>上传失败(部分被上传)!";
            break;    
        case 4: echo "<script>alert('上传失败,没有任何部分被上传!');history.back();</script>上传失败,没有任何部分被上传!";
            break;    
    }
    exit;
}

// 后端判断上传文件的大小
if($_FILES['userfile']['size'] > MAX_SIZE){
    echo "<script>alert('上传失败,上传文件超过限定值大小(由后端大小检测模块发现)!');history.back();</script>上传失败,上传文件超过限定值大小(由后端大小检测模块发现)!";
    exit;
}

// 只允许上传图片
$file_Extension_string = strrev($_FILES['userfile']['name']); // 从$_FILES数组中获取name字段并反转字符串
$file_Extension_array = explode('.',$file_Extension_string); // 以.号为分割拆分成为新的数组
$houZhui = strrev($file_Extension_array[0]); // 反转反转过的扩展名字符串存为$houZhui
// 以下两行代表的两种方法在实际应用中仅保留第二种即可(第一种抓包篡改Content-Type即可绕过,第二种即便抓包篡改Content-Disposition绕过检测也无法执行后续恶意操作‘后缀名都变了’)
$fileMimes = array('image/jpeg','image/png','image/gif'); //允许上传的文件类型,实际应用时记得写前面(方便维护人员调试)
$accepted_houzhui = array('jpg','jpeg','png','gif'); //允许上传的文件扩展名类型,实际应用时记得写前面(方便维护人员调试)
// 判断类型/后缀是否同时分别包含在两个数组
if (is_array($fileMimes)) {
    if (!in_array($_FILES['userfile']['type'],$fileMimes) || !in_array($houZhui,$accepted_houzhui)) {
        echo "<script>alert('上传失败,本站只允许上传jpg/png/gif格式的图片,请重新选择!(由后端类型检测模块发现)');history.back();</script>上传失败,本站只允许上传jpg/png/gif格式的图片,请重新选择!(由后端类型检测模块发现)";
        exit;
    }
}

// 判断目录是否存在,若无则创建
if(!is_dir(MuLu)){
    mkdir(MuLu,0777);
}

// 以上模块均没异常,开始将接收到的临时文件按照本规则重命名并移动到指定目录
if (is_uploaded_file($_FILES['userfile']['tmp_name'])) {

    if(!move_uploaded_file($_FILES['userfile']['tmp_name'],MuLu.$_FILES['userfile']['name'])){
        echo "<script>alert('【后端错误】文件移动失败!');history.back();</script>【后端错误】文件移动失败!";
        exit;
    }
    
} else {
    echo "<script>alert('【后端错误】临时文件夹找不到上传的文件!');history.back();</script>【后端错误】临时文件夹找不到上传的文件!";
    exit;
}

// 以上都正常执行,则认为文件上传成功,并显示上传成功的提示
echo "<script>alert('文件上传成功!');location.href='upload.html';</script>文件上传成功!";

// 图片上传成功的提示,随即显示图片预览
// echo "<script>alert('文件上传成功!');location.href='upload_view.php?url=".htmlspecialchars($_FILES['userfile']['name'])."';</script>";

?>

upload_view.php

<?php

$url = $_GET['url'];

echo "<img src=".'upload/'.$url." style='height: 50%; width:50%;'>";


?>

参考资料:

https://www.cnblogs.com/picaso/archive/2012/04/01/2427873.html

https://www.jb51.net/article/60821.htm

最后修改:2021 年 06 月 25 日 10 : 15 PM

发表评论