确保 PHP 应用程序的安全四

来源:互联网 发布:百度软件助手 编辑:程序博客网 时间:2024/04/19 06:07

防止用户操纵 GET 变量

在前一节中,防止了用户使用畸形的密码进行登录。如果您很聪明,应该应用您学到的方法,确保对 SQL 语句的所有用户输入进行转义。

但是,用户现在已经安全地登录了。用户拥有有效的密码,并不意味着他将按照规则行事 —— 他有很多机会能够造成损害。例如,应用程序可能允许用户查看特殊的内容。所有链接指向 template.php?pid=33template.php?pid=321 这样的位置。URL 中问号后面的部分称为查询字符串。因为查询字符串直接放在 URL 中,所以也称为 GET 查询字符串

在 PHP 中,如果禁用了 register_globals,那么可以用 $_GET['pid'] 访问这个字符串。在 template.php 页面中,可能会执行与清单 8 相似的操作。


清单 8. 示例 template.php

<?php

$pid = $_GET['pid'];

//we create an object of a fictional class Page
$obj = new Page;
$content = $obj->fetchPage($pid);
//and now we have a bunch of PHP that displays the page
//......
//......

?>

这里有什么错吗?首先,这里隐含地相信来自浏览器的 GET 变量 pid 是安全的。这会怎么样呢?大多数用户没那么聪明,无法构造出语义攻击。但是,如果他们注意到浏览器的 URL 位置域中的 pid=33,就可能开始捣乱。如果他们输入另一个数字,那么可能没问题;但是如果输入别的东西,比如输入 SQL 命令或某个文件的名称(比如 /etc/passwd),或者搞别的恶作剧,比如输入长达 3,000 个字符的数值,那么会发生什么呢?

在这种情况下,要记住基本规则,不要信任用户输入。应用程序开发人员知道 template.php 接受的个人标识符(PID)应该是数字,所以可以使用 PHP 的 is_numeric() 函数确保不接受非数字的 PID,如下所示:


清单 9. 使用 is_numeric() 来限制 GET 变量
<?php

$pid = $_GET['pid'];

if (is_numeric($pid)){

    
//we create an object of a fictional class Page
    $obj = new Page;
    
$content = $obj->fetchPage($pid);
    
//and now we have a bunch of PHP that displays the page    
    //......
    //......

}else{
    
//didn't pass the is_numeric() test, do something else!
}?>

这个方法似乎是有效的,但是以下这些输入都能够轻松地通过 is_numeric() 的检查:

  • 100 (有效)
  • 100.1 (不应该有小数位)
  • +0123.45e6 (科学计数法 —— 不好)
  • 0xff33669f (十六进制 —— 危险!危险!)

那么,有安全意识的 PHP 开发人员应该怎么做呢?多年的经验表明,最好的做法是使用正则表达式来确保整个 GET 变量由数字组成,如下所示:


清单 10. 使用正则表达式限制 GET 变量
<?php
$pid = $_GET['pid'];
<b>
if (strlen($pid)){
    
if (!ereg("^[0-9]+$",$pid)){
        
//do something appropriate, like maybe logging 
        them out or sending them back to home page
    }
}
else{
    
//empty $pid, so send them back to the home page
}
</b>
    
//we create an object of a fictional class Page, which is now
    //moderately protected from evil user input

    $obj = new Page;
    
$content = $obj->fetchPage($pid);
    
//and now we have a bunch of PHP that displays the page    
    //......
    //......

?>

需要做的只是使用 strlen() 检查变量的长度是否非零;如果是,就使用一个全数字正则表达式来确保数据元素是有效的。如果 PID 包含字母、斜线、点号或任何与十六进制相似的内容,那么这个例程捕获它并将页面从用户活动中屏蔽。如果看一下 Page 类幕后的情况,就会看到有安全意识的 PHP 开发人员已经对用户输入 $pid 进行了转义,从而保护了 fetchPage() 方法,如下所示:


清单 11. 对 fetchPage() 方法进行转义
<?php
class Page{
  
function fetchPage($pid){
        
$sql = "select pid,title,desc,kw,content,
        status from page where pid='
        
".mysql_real_escape_string($pid)."'";
        
//etc, etc....
    
    }

}
?>

您可能会问,“既然已经确保 PID 是数字,那么为什么还要进行转义?” 因为不知道在多少不同的上下文和情况中会使用 fetchPage() 方法。必须在调用这个方法的所有地方进行保护,而方法中的转义体现了纵深防御的意义。

如果用户尝试输入非常长的数值,比如长达 1000 个字符,试图发起缓冲区溢出攻击,那么会发生什么呢?下一节更详细地讨论这个问题,但是目前可以添加另一个检查,确保输入的 PID 具有正确的长度。您知道数据库的 pid 字段的最大长度是 5 位,所以可以添加下面的检查。


清单 12. 使用正则表达式和长度检查来限制 GET 变量
<?php
$pid = $_GET['pid'];

if (strlen($pid)){
    
if (!ereg("^[0-9]+$",$pid&& strlen($pid> 5){
        
//do something appropriate, like maybe logging 
        them out or sending them back to home page
    }
}
else{
    
//empty $pid, so send them back to the home page
}
    
//we create an object of a fictional class Page, which is now
    //even more protected from evil user input

    $obj = new Page;
    
$content = $obj->fetchPage($pid);
    
//and now we have a bunch of PHP that displays the page    
    //......
    //......

?>
现在,任何人都无法在数据库应用程序中塞进一个 5,000 位的数值 —— 至少在涉及 GET 字符串的地方不会有这种情况。想像一下黑客在试图突破您的应用程序而遭到挫折时咬牙切齿的样子吧!而且因为关闭了错误报告,黑客更难进行侦察。
原创粉丝点击