5.10开发安全的代码
这里我们讨论如何使我们的程序运行更安全,这是开发者的职责,应该切实的把握,避免错误发生。因此,我们需要开发一些技术去避免因用户不规范输入而给我们造成的麻烦。
5.10.1用户输入验证
一个非常重要的技术是如何从用户的输入中有效验证数据,从而保护我们的网站程序与数据。我们需要检查用户输入的全部内容:不管这个数据是从Cookies,GET方法或POST方法来的数据,我们都要检验。
除了在php.ini文件中把register_globals设置为Off外,还要把错误级别修改为E_ALL | E_STRICT,这样就可以阻止从外部请求的数据中生成全局变量,后面的设置是把错误级别设置为打开初始化变量的警告错误。
对于不同类型数据的表单提交,我们可以使用不同的方法来处理。如果我们需要一个参数通过URL的GET方法提交一个整型值,那么在程序中就要把这个参数强制转换为一个整型值,如以下代码所示。
<?php
if (!isset($_GET['prod_id'])) {
echo "错误, product ID不能为空";
}else{
/* 强制转换为数值型变量 */
$product_id = (int) $_GET['prod_id'];
}
?>
复制代码 除了上面的方法,PHP 5还提供了一组叫做ctype的外部扩展库,它提供一个非常迅速的机制,专门针对字符串内容的校验功能。
我们看一个应用ctype扩展的例子:
<?php
if (!ctype_alnum($_GET['login'])) {
echo "输入项必须为英文字符或数字0-9。";
}
if (!ctype_alpha($_GET['captcha'])) {
echo "输入项必须为英文字符(大小写均可)";
}
if (!ctype_xdigit($_GET['color'])) {
echo "输入项必须填写一个16进制数字。";
}
?>
复制代码
5.10.3数据过滤 - PECL filter扩展
PHP 5的PECL扩展库提供了一个新功能-filter扩展。使用filter可以极大的简化表单验证的编码量,尤其是对PHP新手而言,对提高程序的安全性很有帮助,从而远离SQL注入和不充分的字符过滤处理。
可以到http://pecl.php.net/package/filter下载最新的版本。在编写本书时它仍处于beta版本。filter软件包提供了数据类型验证和数据编码两个功能,它提供以下几个主要函数:
filter_data——用于过滤数据;
filter_input——用于表单提交内容的过滤;
filter_var——变量内容的过滤,与filter_data和is_int()标准函数类似。
这3个函数绝大部分的功能都很类似,只不过应用于不同的场合,它们使用的参数也是通用的,如表5-5所示。
表5-5
常量名称 | 功 能 | | FILTER_VALIDATE_INT | 验证为整数,可以指定范围 | | FILTER_VALIDATE_FLOAT | 验证为浮点数 | | FILTER_ VALIDATE_REGEXP | 匹配一个PCRE正则表达式模式 | | FILTER_ VALIDATE_URL | 匹配一个URL | | FILTER_ VALIDATE_EMAIL | 匹配一个email地址 | | FILTER_SANITIZE_STRING | 去除超文本标签 | | FILTER_SANITIZE_ENCODED | 对字符串使用URL编码 | | FILTER_VALIDATE_IP | 验证值是否为IP地址 |
下面举例说明,请见如下脚本:
<?php
var_dump(filter_data([email=]'dujiang@ikang.com'[/email], FILTER_VALIDATE_EMAIL));
var_dump(filter_data('sobooo.com', FILTER_VALIDATE_EMAIL));
?>
复制代码 该脚本会输出如下的内容:
string(15) "dujiang@ikang.com"
NULL
由于字符串“sobooo.com”因为不是邮件地址的格式,因此被过滤后,显示为NULL值。
再看下面使用filter_input验证表单的脚本例子:
<?php
// 如使用FILTER_VALIDATE_INT验证用户输入的QQ号码,表单有一个文本框名字为qq:
$qq = filter_input(INPUT_POST, 'qq', FILTER_VALIDATE_INT);
if (!empty($qq)) {
echo "<p>您的QQ号码: $qq</p>\n";
} else {
echo '<p>请输入正确的QQ号码(应为纯数字)</p>';
}
//使用URL 编码
$url = "http://post.sina.com.cn/file.php?a=1&b=编码";
//显示被编码后的URL地址
echo filter_var($url, FILTER_SANITIZE_ENCODED);
?>
复制代码 第3个例子比较实用,这个脚本代码可验证IP是否正确,并确认是否是内部网IP,代码如下所示。
<?php
// 验证IP v4地址
$ip = "192.168.0.23";
if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === False){
echo "$ip 是非法IP地址";
}else {
echo "$ip 是正确的IP地址";
//验证IP是公网IP还是私有IP地址
if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE) === FALSE)
{
echo "$ip 为内部网私有IP地址";
}else{
echo "$ip 为公网IP地址";
}
}
?>
复制代码 因为PHP 5.2之后已经捆绑了filter扩展,但这一功能一直在升级,你可以查看最新的input_filter动态以及源代码,包括PHP创始人lerdof的个人网站:http://lerdorf.com/php/input_filter.txt
5.10.4input_get()函数
input_get()函数用来获取外部变量,如POST,GET全局数组,格式如下:
mixed input_get ( int type,string variable_name [,int filter [,mixed flags [,string charset]]] )
其中的几个参数含义如下所述。
type:该参数值可以是以下几个值中的一个,它们是INPUT_GET,INPUT_POST,INPUT_COOKIE,INPUT_SERVER,INPUT_ENV,INPUT_SESSION,还有一个99,目前是用来对$_REQUEST进行过滤。
variable_name:变量名。
filter:同filter_data,默认值为FILTER_DEFAULT。
flags:过滤标志。
charset:使用的字符集。
下面是一个使用input_get()函数的例子,代码如下:
<?php
$search_html = input_get(INPUT_GET, 'search', FILTER_SANITIZE_SPECIAL_CHARS);
$search_url = input_get(INPUT_GET, 'search', FILTER_SANITIZE_ENCODED);
echo "您已经搜索到 $search_html.\n";
echo "<a href='?search=$search_url'>重新查询.</a>";
?>
复制代码 该程序输出为:
您已经搜索到e & son.<br />
<a href='?search=Me%20%26%20son'>重新查询.</a>
在上面的代码中,$_GET['search']分别被两种不同过滤器过滤,产生的值也不相同,第一行的filter执行了类htmlspecialchars的操作,第二行则进行了urlencode操作。
说明:在PHP 5.1.4版本之前,不能用filter扩展库。
5.10.5路径检测
PHP应用程序应该安全地进行文件处理,在做操作时一定要进行验证,避免存取文件时访问一些系统或安全级别较高的文件,比如,用户使用如下的URL访问:
http://www.ourwebsite.com/script.php?path=../../etc/passwd
而我们的程序是这样写的:
<?php
$fp = fopen("/home/dir/{$_GET['path']}", "r");
?>
复制代码 这样的代码将允许打开服务器系统任意目录下的文件,这是异常危险的。
PHP中有一个basename()的函数,可以用它来引导一个路径并移动每个文件,请见如下代码:
<?php
$_GET["path"] = basename($_GET['path']);
//只有文件存在的情况才能打开文件
if (file_exists("/home/dir/{$_GET['path']}")) {
$fp = fopen("/home/dir/{$_GET['path']}", "r");
}
?>
复制代码 最好的解决方法是建立这个用户可以使用的文件白名单,我们在一个模板上建立允许打开的文件,如果有,则允许用户打开该文件。
5.10.6魔法引用magic_quotes_gpc
PHP提供magic_quotes_gpc魔法引用功能来保护我们的网站系统免受攻击,它会自动从用户的输入串中过滤特殊字符(',",\,\0 (NULL)),并减慢输入的过程。
检查使用magic_quotes_gpc的代码样例:
if (get_magic_quotes_gpc()) { //检查magic_quotes_gpc的打开状态
function strip_quotes(&$var) {
if (is_array($var){
array_walk($var, 'strip_quotes');
} else{
$var = stripslashes($var);
}
}//end func
// 处理GPC
foreach (array('GET','POST','COOKIE') as $v){
if (!empty(${"_".$v})){
array_walk(${"_".$v}, 'strip_quotes');
}
}
// 处理上传时的文件名称
if (!empty($_FILES)){
foreach ($_FILES as $k => $v) {
$_FILES[$k]['name'] = stripslashes($v['name']);
}
}
}
复制代码 PHP提供这个参数的初衷是好的,但是该功能从出现以来一直备受争议。实践表明,如果采用magic_quotes_gpc,则与不使用该函数相比,需要两倍多的内存来处理每条输入的元素,因此,如果非必要,我们可以在php.ini文件把该参数设置为关闭,不使用该功能,转用其他的方法为来处理。
5.10.7其他高效的解决方案
由于magic_quotes_gpc的效率较低,我们使用其他方法来代替magic_quote_gpc魔法引用的功能,如下脚本:if (get_magic_quotes_gpc()) {
$in = array(&$_GET, &$_POST, &$_COOKIE);
while (list($k,$v) = each($in)) {
foreach ($v as $key => $val) {
if (!is_array($val)) {
$in[$k][$key] = stripslashes($val);
continue;
}
$in[] =& $in[$k][$key];
}
}
unset($in);
}
复制代码 5.11小结
在本章,我们一起讨论了在PHP 5环境中,表单的基本构成、原理,并且对用户表单提交的处理方法,数据验证等做了详细的讲述。
在做表单处理上,介绍了三种方法,这样可使你全面掌握PHP对表单的处理功能,本章还讲述了不太常用但有时候也会用到的import_request_variable()函数等,希望你在查看其他人编写的源码时遇到这样的函数而不至于迷惑。
在下一章,我们将学习在PHP中对数据的加密技术。 |