zoukankan      html  css  js  c++  java
  • 你不知道的文件上传漏洞

    漏洞描述

    开发中文件上传功能很常见,作为开发者,在完成功能的基础上我们一般也要做好安全防护。
    文件处理一般包含两项功能,用户上传和展示文件,如上传头像。

    文件上传攻击示例
    upload.php

    <?php
    $uploaddir = 'uploads/'; 
    $uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
    if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)){
        echo "File is valid, and was successfully uploaded.
    ";
    } 
    else {
        echo "File uploading failed.
    ";
    }
    ?>

    upload.html

    <form name="upload" action="upload.php" method="POST" ENCTYPE="multipart/formdata">
    Select the file to upload: <input type="file" name="userfile">
    <input type="submit" name="upload" value="upload">
    </form>

    上述代码未经过任何验证,恶意用户可以上传php文件,代码如下

    <?php eval($_GET['command']);?>

    恶意用户可以通过访问 如http://server/uploads/shell.php?command=phpinfo(); 来执行远程命令

    Content-type验证

    upload.php

    <?php
    if($_FILES['userfile']['type'] != "image/gif") {//获取Http请求头信息中ContentType
        echo "Sorry, we only allow uploading GIF images";
        exit;
    }
    $uploaddir = 'uploads/';
    $uploadfile = $uploaddir.basename($_FILES['userfile']['name']);
    if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)){
        echo "File is valid, and was successfully uploaded.
    ";
    } else {
        echo "File uploading failed.
    ";
    }
    ?>


    该方式是通过Http请求头信息进行验证,可通过修改Content-type ==> image/jpg绕过验证,可以通过脚本或BurpSuite、fiddle修改
    如下
    Content-Disposition: form-data; name="userfile"; filename="shell.php"
    Content-Type: image/gif

    图片类型验证
    该方法通过读取文件头中文件类型信息,获取文件类型

    备注:如JPEG/JPG文件头标识为FFD8

    upload.php

    <?php
    $imageinfo = getimagesize($_FILES['userfile']['tmp_name']);
    if($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg') {
        echo "Sorry, we only accept GIF and JPEG images
    ";
        exit;
    }
    $uploaddir = 'uploads/';
    $uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
    if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)){
        echo "File is valid, and was successfully uploaded.
    ";
    } else {
        echo "File uploading failed.
    ";
    }
    ?>

    可以通过图片添加注释来绕过此验证。
    如添加注释<?php phpinfo(); ?>,保存图片后将其扩展名改为php,则可成功上传。
    上传成功后访问该文件则可看到如下显示

    文件扩展名验证

     通过黑名单或白名单对文件扩展名进行过滤,如下代码

    upload.php

    <?php
    $blacklist = array(".php", ".phtml", ".php3", ".php4");
    foreach ($blacklist as $item) {
    if(preg_match("/$item$/i", $_FILES['userfile']['name'])) {
        echo "We do not allow uploading PHP files
    ";
        exit;
    }
    }
    $uploaddir = 'uploads/';
    $uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
    if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)){
        echo "File is valid, and was successfully uploaded.
    ";
    } else {
        echo "File uploading failed.
    ";
    }
    ?>

    当黑名单不全,构造特殊文件名可以绕过扩展名验证

    直接访问上传的文件
    将上传文件保存在非web root下其他文件夹下,可以防止用户通过路径直接访问到文件。
    upload.php

    <?php
    $uploaddir = 'd:/uploads/';
    $uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
    if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
      echo "File is valid, and was successfully uploaded.
    ";
    } else {
      echo "File uploading failed.
    ";
    }
    ?>

    用户不可以直接通过http://localhost/uploads/ 来访问文件,必须通过view.php来访问
    view.php

    <?php
    $uploaddir = 'd:/uploads/';
    $name = $_GET['name'];
    readfile($uploaddir.$name);
    ?>

    查看文件代码未验证文件名,用户可以通过例如http://localhost/view.php?name=..//php/upload.php,查看指定的文件


    解决漏洞示例

    upload.php

    <?php
    require_once 'DB.php';
    $uploaddir = 'D:/uploads/'; 
    $uploadfile = tempnam($uploaddir, "upload_");
    if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
      $db =& DB::connect("mysql://username:password@localhost/database");
      if(PEAR::isError($db)) {
        unlink($uploadfile);
        die "Error connecting to the database";
      }
      $res = $db->query("INSERT INTO uploads SET name=?, original_name=?,mime_type=?",
          array(basename($uploadfile,basename($_FILES['userfile']['name']),$_FILES['userfile']['type']));   if(PEAR::isError($res)) {     unlink($uploadfile);     die "Error saving data to the database. The file was not uploaded";   }   $id = $db->getOne('SELECT LAST_INSERT_ID() FROM uploads');   echo "File is valid, and was successfully uploaded. You can view it <a href="view.php?id=$id">here</a> "; } else {   echo "File uploading failed. "; } ?>

      view.php

    <?php
    require_once 'DB.php';
    $uploaddir = 'D:/uploads/';
    $id = $_GET['id'];
    if(!is_numeric($id)) {
      die("File id must be numeric");
    }
    $db =& DB::connect("mysql://root@localhost/db");
    if(PEAR::isError($db)) {
      die("Error connecting to the database");
    }
    $file = $db->getRow('SELECT name, mime_type FROM uploads WHERE id=?',array($id), DB_FETCHMODE_ASSOC);
    if(PEAR::isError($file)) {
      die("Error fetching data from the database");
    }
    if(is_null($file) || count($file)==0) {
      die("File not found");
    }
    header("Content-Type: " . $file['mime_type']);
    readfile($uploaddir.$file['name']);
    ?>

    上述代码文件名随机更改,文件被存储在web root之外,用户通过id在数据库中查询文件名,读取文件,可以有效的阻止上述漏洞发生

    总结

    通过以上示例分析,可总结一下几点

    1.文件名修改,不使用用户上传的文件名

    2.用户不可以通过上传路径直接访问文件

    3.文件查看采用数据库获取文件名,从而在相应文件服务器读取文件

    4.文件上传限制文件大小,个人上传数量等

    作者:简简人事
    出处:http://www.cnblogs.com/codeon/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
  • 相关阅读:
    数据库01 /Mysql初识、基本指令、数据库密码相关、创建用户及授权
    数据库/MySQL的安装
    Python并发编程06 /阻塞、异步调用/同步调用、异步回调函数、线程queue、事件event、协程
    Python并发编程05 /死锁现象、递归锁、信号量、GIL锁、计算密集型/IO密集型效率验证、进程池/线程池
    Python并发编程04 /多线程、生产消费者模型、线程进程对比、线程的方法、线程join、守护线程、线程互斥锁
    Python并发编程03 /僵孤进程,孤儿进程、进程互斥锁,进程队列、进程之间的通信
    python并发编程02 /多进程、进程的创建、进程PID、join方法、进程对象属性、守护进程
    Python并发编程01 /操作系统发展史、多进程理论
    java继承内部类问题(java编程思想笔记)
    递归遍历文件夹中的文件
  • 原文地址:https://www.cnblogs.com/codeon/p/5883666.html
Copyright © 2011-2022 走看看