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攻击

写在最后:安全是相对的,此文是希望大家能够认真对待一些web漏洞,在探索的过程中会遇到很多有趣的。本文如有遗漏或不正确之处,欢迎在评论区留言。

 

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值