你不知道的文件上传漏洞php代码分析


Posted in PHP onSeptember 29, 2016

漏洞描述

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

文件上传攻击示例

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.\n";
} 
else {
 echo "File uploading failed.\n";
}
?>

upload.html

<form name="upload" action="upload1.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.\n";
} else {
 echo "File uploading failed.\n";
}
?>

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

图片类型验证

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

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

你不知道的文件上传漏洞php代码分析

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\n";
 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.\n";
} else {
 echo "File uploading failed.\n";
}
?>

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

你不知道的文件上传漏洞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\n";
 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.\n";
} else {
 echo "File uploading failed.\n";
}
?>

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

直接访问上传的文件

将上传文件保存在非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.\n";
} else {
echo "File uploading failed.\n";
}
?>

用户不可以直接通过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>\n";
} else {
echo "File uploading failed.\n";
}
?>

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.文件上传限制文件大小,个人上传数量等

 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
windows下升级PHP到5.3.3的过程及注意事项
Oct 12 PHP
PHP学习笔记之一
Jan 17 PHP
PHP可逆加密/解密函数分享
Sep 25 PHP
基于MySQL体系结构的分析
May 02 PHP
解析curl提交GET,POST,Cookie的简单方法
Jun 29 PHP
php将字符串转换成16进制的方法
Mar 17 PHP
学习php设计模式 php实现模板方法模式
Dec 08 PHP
Yii2 GridView实现列表页直接修改数据的方法
May 16 PHP
PHP实现的下载远程文件类定义与用法示例
Jul 05 PHP
PHP PDO和消息队列的个人理解与应用实例分析
Nov 25 PHP
PHP 对象继承原理与简单用法示例
Apr 21 PHP
PHP强制转化的形式整理
May 22 PHP
PHP的Json中文处理解决方案
Sep 29 #PHP
PHP二分查找算法示例【递归与非递归方法】
Sep 29 #PHP
PHP快速排序quicksort实例详解
Sep 28 #PHP
PHP实现QQ快速登录的方法
Sep 28 #PHP
PHP自定义错误用法示例
Sep 28 #PHP
PHP构造函数与析构函数用法示例
Sep 28 #PHP
PHP设计模式之工厂模式与单例模式
Sep 28 #PHP
You might like
PHP获取url的函数代码
2011/08/02 PHP
Yii2.0 模态弹出框+ajax提交表单
2016/05/22 PHP
PHP里的$_GET数组介绍
2019/03/22 PHP
laravel解决迁移文件一次删除创建字段报错的问题
2019/10/24 PHP
HR vs ForZe BO3 第二场 2.13
2021/03/10 DOTA
实例:用 JavaScript 来操作字符串(一些字符串函数)
2007/02/15 Javascript
javaScript parseInt字符转化为数字函数使用小结
2009/11/05 Javascript
asp.net网站开发中用jquery实现滚动浏览器滚动条加载数据(类似于腾讯微博)
2012/03/14 Javascript
JS判断页面加载状态以及添加遮罩和缓冲动画的代码
2012/10/11 Javascript
jquery ajax方式直接提交整个表单核心代码
2013/08/15 Javascript
jQuery如何实现点击页面获得当前点击元素的id或其他信息
2014/01/09 Javascript
浅谈javascript 函数属性和方法
2015/01/21 Javascript
JS使用正则截取两个字符串之间的字符串实现方法详解
2017/01/06 Javascript
React创建组件的三种方式及其区别
2017/01/12 Javascript
js时间戳与日期格式之间转换详解
2017/12/11 Javascript
Vue.js实现的表格增加删除demo示例
2018/05/22 Javascript
JS伪继承prototype实现方法示例
2018/06/20 Javascript
Node.js 使用request模块下载文件的实例
2018/09/05 Javascript
微信小程序tabbar底部导航
2018/11/05 Javascript
ES6中的迭代器、Generator函数及Generator函数的异步操作方法
2019/05/12 Javascript
微信小程序3种位置API的使用方法详解
2019/08/05 Javascript
jquery轮播图插件使用方法详解
2020/07/31 jQuery
[00:35]DOTA2上海特级锦标赛 Newbee战队宣传片
2016/03/03 DOTA
探究数组排序提升Python程序的循环的运行效率的原因
2015/04/01 Python
简单了解Django模板的使用
2017/12/20 Python
python无限生成不重复(字母,数字,字符)组合的方法
2018/12/04 Python
Python 词典(Dict) 加载与保存示例
2019/12/06 Python
python把一个字符串切开的实例方法
2020/09/27 Python
J2EE模式面试题
2016/10/11 面试题
中学生个人自我评价
2014/02/06 职场文书
学习党的群众路线对照检查材料
2014/09/29 职场文书
2014幼儿园家长工作总结
2014/11/10 职场文书
毕业设计致谢语
2015/05/14 职场文书
2016年社区创先争优活动总结
2016/04/05 职场文书
咖啡厅里的创业计划书
2019/08/21 职场文书
仅仅使用 HTML/CSS 实现各类进度条的方式汇总
2021/11/11 HTML / CSS