文件上传的流程
上传必须由POST方式的file类型表单提交,被提交的地方 一定是一个php程序,用户在表单使用file类型的域。选在一个自己电脑上的文件,提交到php程序以后 其实就已经完成了一个上传过程,即使这个php代码什么都不写上传依然有效,所谓上传指的是从用户计算机发送一个文件到网站 严格来说是发送到服务器,而且是装有支持上传的解释器的服务器,才支持使用表单上传,我们的php解释器就是其中之一 既然如此 文件的上传到哪儿呢 这个是由php配置决定的,默认情况下 php允许每次上传的2M大小的文件会上传到系统的临时文件夹中,上传的位置和上传的大小配置 我们可以在php.in配置文件找到分别是upload_max_filesize upload_tmp_dir 这两项分别决定了每次上传的最大文件和上传位置,默认情况下上传的路径不需要设置 他会上传到系统的临时文件夹里 win系统在c:\windows\temp 如果我们指定了位置 他就会上传到指定的位置,之前提到上传必须由POST方式的file类型表单提交 ,HTML 代码是这样的
1 <form enctype="multipart/form-data" method="post" action="upload.php">
2 <input type="file" name="upload" />
3 <input type="submit" value="上传" />
4 </form>
method一定是post 另外 光这样是不够的 还要指定编码enctype 告诉服务器 这次提交由文件需要上传。enctype="multipart/form-data" 如果制作文件上传表单上面一定要加上这个,php才知道 此次提交含有文件上传 只要表单写成这样指定的action的php程序存在 这次提交 就一定会产生一个上传的过程。上传文件的表单 必须是file类型
<input type="file" name="upload" /> 显示效果如下 点击上传选择一个文件以后 提交表单到php程序 一个上传过程就完成了 。这个时候文件会上传到指定的位置,但是文件上传到这里没有用 文件不再网站目录下 上传了也无法访问,那怎么办呢 此时把他复制过来copy函数 可以帮我们解决这个问题 函数原型为copy(源文件路径,要保存的路径) 不同的php环境配置的上传路径不一样 那怎么办呢 我们无法确定上传目录的位置 这个时候我们会用到一个预定义变量 $_FILES 和$_POST类似 区别是他只针对于file类型的表单 我们试一下打印结果如下
Array ( [upload] => Array ( [name] => 记录.docx [type] => application/vnd.openxmlformats-officedocument.wordprocessingml.document [tmp_name] => C:\Windows\Temp\php2AB9.tmp [error] => 0 [size] => 16113 ) )返回的是一个二维数组upload就是我们在file表单里指定的name 这一点和post是一样的 但是他里面夹带的数据量就比较多了 name 文件名 type文件类型 tmp_name 在临时文件夹的路径 error 错误信息 size 大小 在数组中的error会返回一些错误信息 一共八中情况 0 1 2 3 4 5 6 7 在php手册中有详细介绍
UPLOAD_ERR_OK
其值为 0,没有错误发生,文件上传成功。
UPLOAD_ERR_INI_SIZE
其值为 1,上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值。
UPLOAD_ERR_FORM_SIZE
其值为 2,上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值。
UPLOAD_ERR_PARTIAL
其值为 3,文件只有部分被上传。
UPLOAD_ERR_NO_FILE
其值为 4,没有文件被上传。
UPLOAD_ERR_NO_TMP_DIR
其值为 6,找不到临时文件夹。PHP 4.3.10 和 PHP 5.0.3 引进。
UPLOAD_ERR_CANT_WRITE
其值为 7,文件写入失败。PHP 5.1.0 引进。
错误5现在已经被抛弃 如果我们要判断文件是否成功上传 可以去error的值来判断 因为是二维数组 我们需要这么写$_FILES['upload']['error']
tmp_name 是已经上传的文件当前的位置和文件名 我们需要制定一个新的位置 要完整的包含文件名的 如果我们需要保持用户上传的文件名我们可以我们可以通过 $_FILES['upload']['name'] 得到。要上传的路径就是我们自定义的了
<?PHP
if($_FILES['upload']['error'] == 0) {
//当前目录下
$path = "./" . $_FILES['upload']['name'];
//把上传的文件复制到当前目录
copy($_FILES['upload']['tmp_name'], $path);
}
上传最基本的两个过程。。
第一个过程,就是用户使用带有 file 和声明了数据的表单,选择一个文件,并提交给 PHP ,这个过程我们干预不了。
第二个过程,我们把上传好的文件,复制到我们想保存的地方。
如果不复制过来的话,PHP 就会在 PHP 执行结束后 ,自动销毁这个文件。什么时候修改默认的上传文件夹的位置呢 系统安全级别比较高的时候 。
我们平时在别人网站上看到上传成功图片之类的。并不是一上传就看到的。。 而是复制过来之后才看到的,因为文件必须在网站的目录下 才能通过http协议访问 但是有些黑客会利用我们的上传程序 上传一些恶意的东西 或者伪造file上传 实际上指向我们的某个php程序 当这个程序运行的过程 就会把我们的某个php程序 复制为。txt之类的文件 这样黑客就可以下载这个文件 已达到查看我们php源代码的目的 以便于找到漏洞 为保证我们程序的安全我们制作文件上传的时候最好别用copy程序 而是使用php指定的一个专用函数move_uploaded_file它和 copy 的区别只有一个:move_uploaded_file 必须是通过表单提交上传来的文件。 move_upload 如果黑客成功上传了一个 .php 文件,他可以在这个 PHP 文件里写上恶意的代码,达到入侵我们服务器的目的 我们需要做一些限制 常见的就是去掉扩展名了,我们可以通过 $_FILES['upload']['name'] 得到用户 原来的文件名,这是一个字符串 扩展名是点号分割的最好一段$ext = end(explode('.', $_FILES['upload']['name'])) 取出扩展名以后,我们可以判断if($ext == "php") die('禁止上传PHP文件');但是这样不太好 要禁止的文件太多了
但是,我们可以考虑一下。我们什么时候会允许用户
上传文件。
会员系统需要上传头像
相册功能上传照片
文档要允许上传各种文档类文件不如做成 允许上传什么类型的文件 代码写的更简单一些
图片不外乎这几种:jpg,jpeg,gif,png,bmp
文档也就是:doc,docx,xls,xlsx,ppt,pdf
当文件名中含有特殊字符的时候。可能会引起我们不能正常访问上传的文件。
为了避免可能出现的问题 最好就是上传以后,帮用户重命名一下文件名。
利用rename函数 上传的文件怎么保存到数据库呢 我们分析下 如果用户上传的是图片我们的最终目的是能在网站上显示图片
在网页上显示图片,使用的是 HTML 码。
<img src="图片路径" />
如果是点击下载。使用的是链接
<a href="文件路径">下载</a>
也就是说,这里需要显示的是“文件路径” 而非文件的内容 也就意味着我们只要在数据库中保存文件的路径就可以了在显示的时候再把这个路径对应显示到html就可以了 并不需要把上传的文件内容保存到数据库中 文件上传是一个危险的操作 如果编写代码不小心 就容易被别有用心的人利用 事实上 还有很多不安全的因素。
但是,每一种不安全的地方。我们都可以通过层层检查的方式。来尽可能的避免。 但也只能是避免。为什么这么说呢。。事实上,各种安全手段。一开始的主要目的,并不是为了防止黑客入侵。而是防止用户错误操作。 如果想通过网页想办法入侵的 其实都可以理解为错误的操作 包含GET POST COOKIE之类的 但这些是不可避免的,那怎么办呢?一直以来这个问题一直在讨论 那就是不要相信用户的任何输入,也就是说任何一种和外界通讯的手法都必须进行安全监察 有一点是绝对的 就是每个传递过来的量 里面的值一定有允许范围 比如我们的翻页$_GET['page']他的值一定是int类型的 最小第一页 最大也就是总量/每页数 那么我们就可以限制他 强行转化为整形 判断数字的大小 ,不建议在GET传递复杂的数据 比如?sql=where id=12 然后在php代码里面$sql="select * from 表明.$_GET['sql']"这种写法和找死没什么区别
宁愿使用 ?id=12
PHP 代码
$id = (int)$_GET['id'];
$sql = "select * from 表名 where id = '$id'";
如果数据来源,是来自外部提交,大家一定要记得,跟据自己的程序执行范围。严格判断用户提交过来的量。 而且 尽可能是准确的值而不要任何的表达式再加上,把用户提交来的数据。进行严格的格式化检查。至少,可以保证我们的程序。在意外的错误操作时,可以安全的运行下去。 比如 转化类型 变换格式 之后再参与php的运算 这里还有一个引号问题 我们常用的查询手法 比如说做搜索的时候
通常是这样子
$key = $_GET['key'];
$sql = "select * from 表名 where id = '$key'";
或者身份验证时。
$sql = "select * from 表名 where user = '$user'";
如果有人在这个时候,恶意提交一个单引号上来会是什么结果。假设,我提交是:test' or 1='1
那代入代码。最终得到的 SQL 语句是
select * from 表名 where user = 'test' or 1='1'
这就意味着,他不管提交上来什么用户名。都有查询结果。。
为了防止这种情况。 我们应该检查用户提交上来的内容。是否含有非法字符。 而在不得不允许用户输入引号的地方。我们应该用 \ 有些 PHP 环境中,并没有打开这个自动转换引号的
if (!get_magic_quotes_gpc()) {
$lastname = addslashes($_POST['lastname']);
} else {
$lastname = $_POST['lastname'];
}
PHP 手册提供的
get_magic_quotes_gpc()
这个函数,可以用来读取 PHP 配置中的自动引号转义功能是否开启。
addslashes 这个函数,可以用于处理“量”里面的引号。没加转义符的。就加上。
如果已经开启了,那就意味着,内容相对安全。可以直接用。
还有一个安全问题。这个问题在 PHP 4 时代很常见 就是允许直接使用来自 GET POST COOKIE 变量。
例如,在 PHP 4 时代,默认是允许这样写的
test.php?a=123;
echo $a; //123
在 PHP 5 时代,默认只能写 echo $_GET['a'],也正是因为安全原因考虑。这个叫:自动全局变量,在 php.ini 中有这一项配置。默认是关闭的。 这个配置项叫 register_globals 有些环境中,是默认打开的。打开状态下,允许直接使用变量名访问 GET POST COOKIE 里的变量。 比如我们的代码,身份验证,本来是用 SESSION 的。
<?PHP
if(empty($_SESSION['username']))die("没有登录");
由于开启了自动注册全局变量。就可以写成
if(empty($username)) die("没有登录");
但是,这个时候,如果有人在地址栏写上
test.php?username=1 ;GET 变量也会被注册成普通变量。于是,这里的身份验证通过了。。。
严重一点的情况
?sql=select……
直接影响我们的
mysql_query($sql);
所以,为了安全。不要开启PHP的这一项功能。
如果你们制作项目使用的服务器。已经开启了这项功能。就要小心了。。除了 GET POST COOKIE 变量要检查。还要检查同名变量。。
有一种写法。可以很方便的检查。
foreach($_GET as $k=>$v) {
if(isset($$k) and $$k == $v) unset($$k);
}
循环检查所有 GET 变量,按索引和值分拆开。
取索引,组成一个变量名。如果这个变量事先存在,并且值和当前 GET 值相同。就灭了它。。
如果我们提交了 test.php?a=1;
代码一开始就写上这个代码。。这个时候前面跟本没有任何的代码。又何来的 $a ,如果偏偏它这个时候存在了。。那就一定不是我们定义的。。灭了它。
这就是所谓的 永远不要相信外部提交来的变量,不管是用户提交的,还是我们自己程序中转来的,进行严格的检查和判断。是有必要的。更不要直接取预定义变量的索引名做为变量名。另外,尽量不要用外部传递来的量,直接参与运算。
比如有一种写法是这样的。
index.php
<?php
$file = $_GET['file'];
include $file . ".php";
?>这样的写法,有个好处。就可以在一个程序里,运行几个不同的程序。 index.php?file=news对于 PHP 来说就是include "news.php"; 直接使用来自外部的变量,后果就是这样index.php?file=http://我的网址/hack 结果对于我们的代码就变成了include "http://我的网址/hack.php";
最后总结一下:
使用外部传递来的值,要进行一定判断或格式化。
上传文件的时候,更要严格检查文件的类型,有必要时要进行强制的文件名重命名。
文件上传,一定是 POST 表单,要声明是数据上传。一定是 file 选择文件。
文件会上传到服务器的临时文件夹,文件夹必须可以访问。
我们需要把上传的文件复制出来。不然它马上会消失。
复制上传过来的文件,应当使用 move_uploaded_file 函数,以确保安全。