首页 » PHP和MySQL Web开发(原书第4版) » PHP和MySQL Web开发(原书第4版)全文在线阅读

《PHP和MySQL Web开发(原书第4版)》16.4 代码的安全性

关灯直达底部

接下来介绍实现安全性的另一个方面:检查所有单个组件并且寻找如何改进其安全性的方法。这里将以检查所有有助于提高代码安全性的地方为开始。尽管不能涵盖所有与安全威胁相关的内容(需要整本书来专门介绍这些话题),我们至少可以给出一些常规指导以及正确的方向。对于本书稍后章节将要设计的PHP特定技术领域,我们将在介绍它们的时候给出相关安全性的问题。

16.4.1 过滤用户输入

在Web应用中,为了提高应用的安全性,我们能做的一件非常重要的事情就是过滤用户输入。

应用的开发人员必须过滤所有来自外部的输入。这并不只是意味着我们必须设计一个假设所有用户都是“骗子”的系统。我们仍然希望用户能够感受他们是受欢迎的,并且感受到我们鼓励他们使用我们的Web应用。我们必须确保已经对系统的任何错误使用都做好准备。

如果能够有效地过滤用户输入,我们就可以减少相当数量的外部威胁,而且大大提高系统的健壮性。即使我们非常确认我们信任用户,也不能确认他们是否拥有某些间谍软件程序或其他能够修改或发送新请求的程序。

既然了解过滤外部用户输入的重要性,我们必须了解相应的措施。

有些时候,我们的应用会给用户提供一些可供选择的值,例如,送货方式(地面、快递还是第二日到达)或省份等。这里,假设有如下所示的简单表单:

<html>

<head>

<title>What be ye laddie?</title>

</head>

<body>

<form action=/"submit_form.php/"method=/"POST/">

<input type=/"radio/"name=/"gender/"/>Male<br/>

<input type=/"radio/"name=/"gender/">Female<br/>

<input type=/"radio/"name=/"gender/"/>None of your Business<br/>

<input type=/"submit/"/>

</form>

</body>

</html>

该表单的运行结果如图16-1所示。对于这个表单,我们可能需要假设当查询submit_form.php脚本中的$_POST[/'gender/']值时,期望获得/"Male/",/"Female/"或/"Other/"三个值之一——可是,我们可能完全错了。

图 16-1 一个性别输入表单

正如前面提到的,Web使用HTTP(一个简单的文本协议)进行操作。以上表单提交将作为具有如下所示结构的文本消息发送给服务器:

POST/gender.php HTTP/1.1

Host:www.yourhostname.com

User-Agent:Mozilla/5.0(Windows;U;Windows NT 6.0;en-US;rv:1.9.0.1)

Gecko/2008070208 Firefox/3.0.1

Content-Type:application/x-www-form-urlencoded

Content-Length:11

gender=Male

然而,这并不能防止某些用户直接连接到我们的Web服务器并且在表单中发送任何值。因此,某些用户可以发送如下所示的信息:

POST/gender.php HTTP/1.1

Host:www.yourhostname.com

User-Agent:Mozilla/5.0(Windows;U;Windows NT 6.0;en-US;rv:1.9.0.1)

Gecko/2008070208 Firefox/3.0.1

Content-Type:application/x-www-form-urlencoded

Content-Length:22

gender=I+like+cookies.

如果我们编写如下所示的代码:

<?php

echo/"<p align=/"center/">

The user/'s gender is:/".$_POST[/'gender/']./".

</p>/";

?>

稍候,就会发现困惑我们自身的问题。更好的策略是验证提交值是否是期望值或允许值,如下所示:

<?php

switch($_POST[/'gender/']){

case/'Male/':

case/'Female/':

case/'Other/':

echo/"<p align=/"center/">Congratulations!

You are:/".$_POST[/'gender/']./".</p>/";

break;

default:

echo/"<p align=/"center/">

<span style=/"color:red;/">WARNING:</span>

Invalid input value for gender specified.</p>/";

break;

}

?>

这样,可能会需要更多代码,但是我们可以确保获得了正确值,这对处理财务相关的敏感数据时将会更加重要。因此,作为一条法则,我们不能假设来自表单的值就会是期望值——必须首先检查提交值。

HTML表单元素没有类型定义,因此只能向服务器传递简单的字符串(用来表示日期、时间或数字)。因此,如果表单中有一个数字域,不能假设或者信任该域被正确地输入了数据。即使在客户端代码足够强大并且能够检查输入值是否为特定类型的环境中,也无法确保这些值不会被直接发送给服务器,正如我们在上一节介绍的。

确认一个值是否为期望类型的简单方法是将其转换成期望类型,然后使用该值,如下所示:

$number_of_nights=(int)$_POST[/'num_nights/'];

if($number_of_nights==0)

{

echo/"ERROR:Invalid number of nights for the room!/";

exit;

}

如果允许用户输入一个以本地化格式输入的日期,例如,美国用户习惯的mm/dd/yy格式,我们可以使用PHP的checkdate函数编写代码来确认提交值是否为真实的日期。

该函数的输入参数为月份,日期以及年份(4位数字),返回值表示该日期是否为有效日期,如下所示:

//split is mbcs-safe via mbstring(see chapter 5)

$mmddyy=split($_POST[/'departure_date/'],/'//');

if(count($mmddyy)!=3)

{

echo/"ERROR:Invalid Date specified!/";

exit;

}

//handle years like 02 or 95

if((int)$mmddyy[2]<100)

{

if((int)$mmddyy[2]>50)

$mmddyy[2]=(int)$mmddyy[2]+1900;

else if((int)$mmddyy[2]>=0)

$mmddyy[2]=(int)$mmddyy[2]+2000;

//else it/'s<0 and checkdate will catch it

}

if(!checkdate($mmddyy[0],$mmddyy[1],$mmddyy[2]))

{

echo/"ERROR:Invalid Date specified!/";

exit;

}

通过过滤和验证输入,我们不仅能够执行常规的错误检查(例如,验证一张机票的起飞日期是否为有效日期),还可以改进系统的安全性。

处理字符串安全的另一个例子就是防止SQL插入攻击,这种攻击已经在介绍在PHP中使用MySQL提到。在这种攻击中,恶意用户将利用安全性较低的代码以及用户权限来执行一些不必要的SQL代码。如果不够仔细,如下的用户名:

kitty_cat;DELETE FROM users;

可能会带来一些问题。

主要有两种方法来防止这种安全突破:

■过滤并转义所有通过SQL发送给数据库服务器的字符串。使用mysql_escape_string,mysqli::real_escape_string或mysqli_real_escape_string函数。

■确认所有输入都符合期望值。如果用户名称最多50个字符并且只能包含字母和数字,我们就可以确认/";DELETE FROM users/"可能就是不允许的字符串。编写可以确保在发送给数据库服务器之前输入值符合期望值的PHP代码意味着可以在数据库给出错误信息之前打印出更有意义的错误信息,从而降低风险。

mysqli扩展新增加了允许单个查询的安全性优点,这些查询可以通过mysqli_query或mysqli::query方法执行。要执行多个查询,必须使用mysqli_multi_query或mysqli::multi_query方法,这些方法将有助于防止潜在的破坏性语句或查询的执行。