zoukankan      html  css  js  c++  java
  • PHP文件上传漏洞原理以及如何防御详解 【转】

    PHP文件上传漏洞
    一、漏洞描述 
    在本人开发过程中文件上传的功能是很常见的,比如一个游戏平台:

    ①用户可以上传自己的头像图片,

    ②用户论坛发表文章时又需要上传图片来丰富自己的文章,

    ③更有甚者游戏开发用户需要上传APK文件等。文件上传功能是十分重要的,

    所以针对这个功能的漏洞就由下面来讨论研究,经过实战例子和跟大家探讨如何防护。

    ​ 为了方便统一讨论,目前我们就以上传图片功能来做讲解,会由浅到深的进行。

    注意:代码中有详细的注释,请新手读者认真阅读。为方便新手读者,代码可能会写的比较原始,有一定基础的读者请自行尝试封装。如有疑问请在评论区留言。谢谢。

    第一种漏洞使用:
    我们先看一个前端HTML代码上传图片的简易版代码:upload.html

    <!DOCTYPE html>
    <html>
    <meta charset="utf-8">
    <title>测试文件上传漏洞</title>
    <body>
    <!-- 下面是一个上传文件的form表单 -->
    <form enctype="multipart/form-data" action="upload_file.php" method="POST" />
    选择你要上传的图片:<br>
    <input name="upload_file" type="file" /><br><br>
    <input type="submit" name="upload" value="上传" />
    </form>
    </body>
    </html>
    上面代码的页面效果:

    一般开发为了快速实现效果就会写出类似下面的代码:upload_file.php

    <?php
    // 防止没有任何文件上传报错
    if (!isset($_FILES['file01'])) {
    echo "Nothing Upload";
    return ;
    }
    // 用一个变量来存储上传文件的object,减少代码。
    $file = $_FILES['file'];
    // 设定好文件的保存路径
    $save_upload_path = "uploads/" . basename($file['name']);
    // 开始上传文件
    if(!move_uploaded_file($file['tmp_name'], $save_upload_path)) {
    echo '您的图片上传失败.';
    return;
    } else {
    echo $save_upload_path . '文件已经成功上传!';
    return;
    }
     上面的代码就是简单的把上传的文件保存到服务器上。然而并没有做任何的过滤或者防护措施。这是很致命的操作。如果此时我们写一个简单的一句话木马入侵就会很容易操控系统。例如:cmd.php

    // 简单的一句话木马
    <?php eval($_GET['cmd']); ?>
    然后我们利用上面html代码上传到服务器的uploads文件中,此时我们假设访问

    http://www.域名.com/uploads/cmd.php?cmd=system('whoami') 

    即可触发我们写好的木马。

    第一个防护:验证文件类型
    我们来动手改下上面uplaod_file.php的文件(下面是新版)

    <?php
    // 防止没有任何文件上传报错
    if (!isset($_FILES['file01'])) {
    echo "Nothing Upload";
    return ;
    }
    // 用一个变量来存储上传文件的object,减少代码。
    $file = $_FILES['file'];
    // 设定好文件的保存路径
    $save_upload_path = "uploads/" . basename($file['name']);


    $uploaded_name = $file[ 'name' ];//文件名称
    $uploaded_type = $file[ 'type' ];//文件类型
    $uploaded_size = $file[ 'size' ];//文件大小
    // 新增步骤:识别文件类型
    if( $uploaded_type !== "image/jpeg" ||
    $uploaded_type !== "image/png" )
    {
    // 这里的图片类型出了png和jpeg外还有很多,例如gif,
    // 但是除非你的网站很open,不然建议不要开放gif的上传控制。
    echo "Upload false, the file type is not allowed";
    return;
    }
    // 识别文件大小,1MB = 1024*1024B,限制2MB
    if($uploaded_size > 1024*1024*2)
    {
    echo "Upload false, the file size is too large";
    return ;
    }

    // 开始上传文件
    if(!move_uploaded_file($file['tmp_name'], $save_upload_path)) {
    echo '您的图片上传失败.';
    return;
    } else {
    echo $save_upload_path . '文件已经成功上传!';
    return;
    }
    以上就是就基本的防御,通过判断文件的类型以及文件的大小来达到限制其他文件的上传。但是已经说了这是最简单的,所以是很容易被破解的,下面来介绍第二种情况。

    第二种漏洞使用:
    有一定基础的PHPer都会做一定基础防御,但是道高一尺魔高一丈。下面我们来介绍一套组合拳:抓包工具:Burpsuite; 中国菜刀:Cknife(Burp Suite 是用于攻击web 应用程序的集成平台,包含了许多工具。)(希望大家能 "善" 用此类工具,心无杂念。)具体如何用这些工具我就不在这里赘述了。

    使用上面的工具能帮助入侵者修改文件的类型甚至是文件的大小,以此来骗过系统程序的判断。有的小伙伴可能会想到控制文件的后缀,其实通过Burpsuite抓包依然是可以将.png的文件变成.php文件运行的。

    所以我们需要全面防护,但是要记住一句话:安全是相对的,没有任何绝对的安全!

    全面防护:最终版upload_file.php
    <?php
    // 防止没有任何文件上传报错
    if (!isset($_FILES['file01'])) {
    echo "Nothing Upload";
    return ;
    }

    // 增加请求头token验证,token的生成方法建议大家可以参考JWT做法。
    if(!checkToken($_REQUEST[ 'user_token' ]))
    {
    echo "Nothing Upload";
    return;
    }

    // 用一个变量来存储上传文件的object,减少代码。
    $file = $_FILES['file'];
    // 设定好文件的保存路径
    $save_upload_path = "uploads/";
    $uploaded_name = $file[ 'name' ]; // 文件名称
    $uploaded_type = $file[ 'type' ]; // 文件类型
    $uploaded_size = $file[ 'size' ]; // 文件大小
    $uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1); // 文件后缀名
    $end_upload_ext = strtolower( $uploaded_ext );
    $uploaded_tmp = $file['tmp_name']; // 临时文件数据
    $save_upload_file = md5( uniqid().$uploaded_name ) . '.' . $uploaded_ext;
    $temp_path = ( ( ini_get( 'upload_tmp_dir' ) == '' ) ? ( sys_get_temp_dir() ) : ( ini_get( 'upload_tmp_dir' ) ) );
    $temp_path .= DIRECTORY_SEPARATOR . md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
    // 识别文件类型
    if( $uploaded_type !== "image/jpeg" ||
    $uploaded_type !== "image/png" )
    {
    // 这里的图片类型出了png和jpeg外还有很多,例如gif,
    // 但是除非你的网站很open,不然建议不要开放gif的上传控制。
    echo "Upload false, the file type is not allowed";
    return;
    }
    // 识别文件大小,1MB = 1024*1024B,限制2MB
    if($uploaded_size > 1024*1024*2)
    {
    echo "Upload false, the file size is too large";
    return ;
    }
    // 识别文件后缀名
    if( $end_upload_ext !== 'jpg' || $end_upload_ext !== 'jpeg' || $end_upload_ext !== 'png' )
    {
    echo "Upload false, the file size is too large";
    return ;
    }

    // 开始上传文件
    if(getimagesize($uploaded_tmp))
    {
    // 将图片重新复制一遍,防止有害数据保留下来
    if( $uploaded_type == 'image/jpeg' ) {
    $img = imagecreatefromjpeg( $uploaded_tmp );
    imagejpeg( $img, $temp_path, 100);
    }
    else if($uploaded_type == 'image/png') {
    $img = imagecreatefrompng( $uploaded_tmp );
    imagepng( $img, $temp_path);
    }
    else
    {
    return ;
    }
    // 释放图片资源
    imagedestroy( $img );
    // 临时保存的图片转存到真实路径下
    if( rename( $temp_path, __dir__ . DIRECTORY_SEPARATOR . $save_upload_path . $target_file ) ) {
    echo "upload file successfully";
    }
    else {
    echo "upload false";
    }
    // 删除临时图片文件(为防止未清理临时文件前程序崩溃等,建议加多一个守护进程清除,以及增加报错记录)
    if( file_exists( $temp_path ) )
    {
    unlink( $temp_path );
    }

    return;
    }

    // 增加csrf防御
    runCsrfToken();
    upload_file.php文件代码详解:

    确保文件数据真实存在
    增加请求token,防止跨域攻击
    然后通过限制文件的类型、文件的后缀名,以及文件大小来控制上传白名单
    uniqid()函数能帮助我们获取微秒级的13位数的时间戳,减少在同一时间上传相同文件名而造成误差的可能。
    通过getimagesize()函数判断该文件是否是真实的图片资源。
    最后通过将图片重新复制成一个新的图片,去除掉有害的数据,留下真实的图片资源。
    最后增加一步防止CSRF攻击

  • 相关阅读:
    mybatis源码(八) Mybatis中的#{} 和${} 占位符的区别
    mybatis源码(七)mybatis动态sql的解析过程下篇
    mybatis源码(六)mybatis动态sql的解析过程上篇
    JDBC的API介绍
    mybatis源码(五)mybatis日志实现
    jmeter: 报错锦集
    python3升级后pip提示TLS/SSL错误问题
    Pytest+Jenkins+Allure
    建议
    Android Studio Button事件的三种方式
  • 原文地址:https://www.cnblogs.com/11a22b33c/p/13354453.html
Copyright © 2011-2022 走看看