你不知道的文件上传漏洞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 相关文章推荐
十天学会php之第六天
Oct 09 PHP
php 生成随机验证码图片代码
Feb 08 PHP
php array的学习笔记
May 10 PHP
用Json实现PHP与JavaScript间数据交换的方法详解
Jun 20 PHP
解析crontab php自动运行的方法
Jun 24 PHP
PHP cURL初始化和执行方法入门级代码
May 28 PHP
PHP使用array_merge重新排列数组下标的方法
Jul 22 PHP
CI框架源码解读之利用Hook.php文件完成功能扩展的方法
May 18 PHP
php for 循环使用的简单实例
Jun 02 PHP
Zend Framework常用校验器详解
Dec 09 PHP
php出租房数据管理及搜索页面
May 23 PHP
php微信公众号开发之快递查询
Oct 20 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
站长助手-网站web在线管理程序 v1.0 下载
2007/05/12 PHP
PHP获取音频文件的相关信息
2015/06/22 PHP
初识通用数据库操作类――前端easyui-datagrid,form(php)
2015/07/31 PHP
Prototype使用指南之base.js
2007/01/10 Javascript
js判断背景图片是否加载成功使用img的width实现
2013/05/29 Javascript
基于JavaScript实现瀑布流效果(循环渐近)
2016/01/27 Javascript
jQuery EasyUI编辑DataGrid用combobox实现多级联动
2016/08/29 Javascript
12 款 JS 代码测试必备工具(翻译)
2016/12/13 Javascript
canvas滤镜效果实现代码
2017/02/06 Javascript
微信小程序 跳转方式总结
2017/04/20 Javascript
jQuery ajax动态生成table功能示例
2017/06/14 jQuery
Angularjs为ng-click事件传递参数
2017/06/15 Javascript
详解vue2.0 transition 多个元素嵌套使用过渡
2017/06/19 Javascript
基于js 本地存储(详解)
2017/08/16 Javascript
详解如何让Express支持async/await
2017/10/09 Javascript
JS笛卡尔积算法与多重数组笛卡尔积实现方法示例
2017/12/01 Javascript
JS兼容所有浏览器的DOMContentLoaded事件
2018/01/12 Javascript
Vue框架下引入ActiveX控件的问题解决
2019/03/25 Javascript
Python中使用select模块实现非阻塞的IO
2015/02/03 Python
Python Numpy 数组的初始化和基本操作
2018/03/13 Python
利用python如何在前程无忧高效投递简历
2019/05/07 Python
python的sorted用法详解
2019/06/25 Python
Python 序列化和反序列化库 MarshMallow 的用法实例代码
2020/02/25 Python
python中yield的用法详解
2021/01/13 Python
优衣库台湾官网:UNIQLO台湾
2019/02/01 全球购物
学校三节实施方案
2014/06/09 职场文书
英语教研活动总结
2014/07/02 职场文书
社区党建工作汇报材料
2014/08/14 职场文书
无财产无子女离婚协议书范文
2014/09/14 职场文书
司法局群众路线教育实践活动整改措施
2014/09/17 职场文书
2014年库房工作总结
2014/11/26 职场文书
2019事业单位个人工作总结范文
2019/08/26 职场文书
oracle DGMGRL ORA-16603报错的解决方法(DG Broker)
2021/04/06 Oracle
基于Golang 高并发问题的解决方案
2021/05/08 Golang
SQL Server使用导出向导功能
2022/04/08 SQL Server
Win11更新失败并提示0xc1900101
2022/04/19 数码科技