你不知道的文件上传漏洞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 相关文章推荐
图象函数中的中文显示
Oct 09 PHP
转PHP手册及PHP编程标准
Dec 17 PHP
session在php5.3中的变化 session_is_registered() is deprecated in
Nov 12 PHP
PHP APC缓存配置、使用详解
Mar 06 PHP
php的zip解压缩类pclzip使用示例
Mar 14 PHP
php jsonp单引号转义
Nov 23 PHP
PHP使用mysql_fetch_row查询获得数据行列表的方法
Mar 18 PHP
php封装的图片(缩略图)处理类完整实例
Oct 19 PHP
php mysql PDO 查询操作的实例详解
Sep 23 PHP
PHP面向对象程序设计继承用法简单示例
Dec 28 PHP
4种Windows系统下Laravel框架的开发环境安装及部署方法详解
Apr 06 PHP
PHP实现Snowflake生成分布式唯一ID的方法示例
Aug 30 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
javascript 可以拖动的DIV(二)
2009/06/26 Javascript
JS 的应用开发初探(mootools)
2009/12/19 Javascript
基于jQuery的获得各种控件Value的方法
2010/11/19 Javascript
javascript中用星号表示预录入内容的实现代码
2011/01/08 Javascript
javascript event 事件解析
2011/01/31 Javascript
jQuery UI 实现email输入提示实例
2013/08/15 Javascript
判断javascript的数据类型(示例代码)
2013/12/11 Javascript
jquery修改网页背景颜色通过css方法实现
2014/06/06 Javascript
Javascript中使用A标签获取当前目录的绝对路径方法
2015/03/02 Javascript
JS实现网页顶部向下滑出的全国城市切换导航效果
2015/08/22 Javascript
JS处理json日期格式化问题
2015/10/01 Javascript
去除html代码里面的script正则方法
2016/05/19 Javascript
javascript 数组的定义和数组的长度
2016/06/07 Javascript
JavaScript面试题大全(推荐)
2016/09/22 Javascript
JavaScript 数组的深度复制解析
2016/11/02 Javascript
初识NodeJS服务端开发入门(Express+MySQL)
2017/04/07 NodeJs
在vue中通过axios异步使用echarts的方法
2018/01/13 Javascript
ES6入门教程之Array.from()方法
2019/03/23 Javascript
layui表格 列自动适应大小失效的解决方法
2019/09/06 Javascript
vue vantUI实现文件(图片、文档、视频、音频)上传(多文件)
2019/10/15 Javascript
vue3 源码解读之 time slicing的使用方法
2019/10/31 Javascript
ant-design-vue 时间选择器赋值默认时间的操作
2020/10/27 Javascript
[03:27]最受玩家喜爱奖提名:PZH_Element 致玩家寄语
2016/12/20 DOTA
python笔记(2)
2012/10/24 Python
Python编写检测数据库SA用户的方法
2014/07/11 Python
仅利用30行Python代码来展示X算法
2015/04/01 Python
在Python web中实现验证码图片代码分享
2017/11/09 Python
python识别图像并提取文字的实现方法
2019/06/28 Python
python自动化UI工具发送QQ消息的实例
2019/08/27 Python
Pytorch中.new()的作用详解
2020/02/18 Python
Python selenium使用autoIT上传附件过程详解
2020/05/26 Python
详解修改Anaconda中的Jupyter Notebook默认工作路径的三种方式
2021/01/24 Python
大专生工程监理求职信
2013/10/04 职场文书
50岁生日感言
2014/01/23 职场文书
小组合作学习反思
2014/02/18 职场文书
优秀共产党员演讲稿
2014/09/04 职场文书