IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Open source  >

使用 CakePHP 快速打造 Web 站点,第 3 部分: 使用 Sanitize 进行保护

如何使用 Sanitize 和 Security 保护 CakePHP 应用程序

developerWorks
文档选项

未显示需要 JavaScript 的文档选项

样例代码


级别: 中级

Duane O'Brien, PHP 开发人员, 自由撰稿人

2006 年 12 月 19 日
更新 2008 年 2 月 21 日

CakePHP 是一种用 PHP 构建 Web 站点的辅助工具,它稳定、可直接用于生产及快速开发。“使用 CakePHP 快速打造 Web 站点” 系列教程向您展示如何使用 CakePHP 构建在线产品目录。

编辑说明:本系列最初发表于 2006 年和 2007 年。自从本系列发表以来,CakePHP 开发人员对 CakePHP 做了重大修改,因此原来的内容过时了。为了反映这些修改并充实本系列的内容,作者修订了本系列的五个部分,使它与 2008 年 1 月发布的 CakePHP 版本保持一致。

简介

使用 CakePHP 快速打造 Web 站点” 系列教程适合希望开始使用 CakePHP 轻松构建应用程序的 PHP 应用程序开发人员学习。通过本系列教程,您将了解如何安装和配置 CakePHP 以及有关 Model-View-Controller(MVC)设计、如何在 CakePHP 中检验用户数据、如何使用 CakePHP helper、如何使用 CakePHP 快速建立并运行应用程序的基本知识。听起来好像有很多东西要学习,但不必担心 — CakePHP 会替您完成其中的大部分工作。

本教程假设您已经学习了 第 1 部分第 2 部分,而且仍然保留为那两个教程设置的工作环境。如果还没有安装 CakePHP,则应当先读完第 1 部分和第 2 部分,并按其内容完成操作,然后再继续学习本教程。

您需要熟悉 PHP 编程语言,基本掌握数据库设计并喜欢实战。





回页首


系统需求

开始之前,需要具备一个工作环境。CakePHP 的最低服务器需求为:

  1. 支持会话(并且最好支持 mod_rewrite)的 HTTP 服务器。本教程采用的是支持 mod_rewrite 的 Apache V2.2.4。
  2. PHP V4.3.2 或更高版本(包括 PHP V5)。本教程采用的是 PHP V5.2.3。
  3. 受支持的数据库引擎。本教程采用的是 MySQL V5.0.4。

还需要准备好一个数据库以供应用程序使用。本教程将提供在 MySQL 中创建任何必需的表的语法。

下载 CakePHP 的最简单方法是访问 CakeForge.org 并下载最新的稳定版本。本教程采用的是 V1.2.0。还可以直接使用来自 Subversion 的每日构建和拷贝。CakePHP Manual 中有更详细的信息(请参阅 参考资料)。





回页首


目前为止的 Tor

第 2 部分 的末尾,您获得了另一个通过增强 Tor 实践您的技能的机会。使用 Bake 为经销商生成视图和控制器,然后检验经销商名称是否是惟一的,修复产品 ACL 中的 bug,更改 products 视图使其仅向可以使用 EditDelete 按钮的用户显示这些按钮。面对如此之多的任务,您是怎样做的?

用 Bake 生成经销商

用 Bake 生成 dealers 控制器和视图应该说是非常简单的。从 /webroot/app 目录中启动 Cake Console。

为了生成控制器,应当在第一个菜单中输入 C 并选择 dealers 控制器。为了生成视图,应当在第一个菜单中输入 V 并再次选择 dealers 控制器。这些步骤实际上与创建 products 控制器和视图所遵循的步骤是相同的。

还必须确保经销商名称是惟一的。实现的方法是在 Dealer 模型中添加一个 beforeValidate 方法,见清单 1。


清单 1. 修改 dealers 中的 add 操作
                
function beforeValidate() {
        if (!$this->id) {
            if ($this->findCount(array('Dealer.title' => 
			$this->data['Dealer']['title'])) > 0) {
                $this->invalidate('title_unique');
                return false;
            }
        }
        return true;
    }

还需要修改经销商的 add 视图,寻找 title_error 错误,就像为用户 register 视图所做的那样。

修复产品 bug

另一项任务是修复 products add 方法中的 bug。正如 第 2 部分 结尾处指出的,任何人都可以添加产品,即使是在用户注销后。要解决此问题,有多种方法。

通过使用访问控制列表(Access Control List,ACL),可以在 products 控制器中创建一个操作,并使用它将对每个经销商 ACO 的 create 访问权授予用户访问请求对象(Access Request Object,ARO)。然后删除此操作,因为不再需要使用它。接下来,修改 dealer add 操作,将经销商 ACO 的 create 权限授予用户的 ARO。最后,在 products add 方法中,进行检查以确保用户具有权限。可能执行如下所示的操作。


清单 2. 在用户的 index 操作中检查用户是否已登录
                
function add() {

  $username = $this->Session->read('user');

  if ($username) {

   ... the rest of your add function goes here

  } else {

    $this->redirect(array('controller' => 
'users', 'action'=>'login'), null, true);

  }

}

看上去很熟悉?应该是这样。该检查与在用户 index 操作中查看用户是否已登录的检查相同。如果用户已登录,那么他显然就是一名具有相应权限的用户。可以说,这是一种巧妙的解决方案,它说明存在多种验证用户的方法。

经销商 ACL

根据目前的情况来看,经销商操作完全不受保护。应当修改这些操作,以便只有用户才能实际使用它们。虽然要想完整地说明此问题需要很长的篇幅,无法在此介绍,但可以(也应该)应用更严格的 ACL。请考虑以下规则:

  • 任何用户都可以添加或查看经销商。
  • 只有创建了经销商的用户才能修改或删除这个经销商。

在应用程序中还可以更进一步,例如:

  • 如果用户已经创建了一个经销商,那么他可以将另一个用户添加到经销关系中。
  • 经销关系中的任何用户都可以修改为这个经销关系创建的任何产品。

可以使用多种其他方法来完成此任务。体验其中的几种。要勇于动手实践!

products 视图增强

需要完成的最后一项任务是修改 products 视图,以使 EditDelete 按钮仅显示给可以编辑或删除产品的用户。完成此任务的最简单方法可能是,从 index 视图中完全删除 EditDelete 按钮。只需根据用户权限检查每个产品,并相应地显示或隐藏按钮即可,但性能可能不理想。

然后,在 view 操作中,可以检查用户是否具有更新或删除产品的权限,并设置用于显示或隐藏视图中的按钮的变量。在控制器的 view 操作中,应当有如下所示的代码。


清单 3. 可能的 view 操作解决方案
                
if ($this->Acl->check($this->Session->read('user'), 
$id.'-'.$product['Product']['title'], 'update'))

{
  $this->set('showUpdate', TRUE);
} else {
  $this->set('showUpdate', FALSE);
}
if ($this->Acl->check($this->Session->read('user'),
$id.'-'.$product['Product']['title'],
'delete'))
{
  $this->set('showDelete', TRUE);
} else {
  $this->set('showDelete', FALSE);
}

另外,在 products/view.ctp 中,需要如下所示的代码:


清单 4. 可能的 products/view.ctp 解决方案
                
<?php if ($showUpdate) { ?>
<li><?php echo $html->link(... your edit link code ... ); ?> </li>
<?php } ?>
<?php if ($showDelete) { ?>
<li><?php echo $html->link(... your edit link code ... ); ?> </li>
<?php } ?>

如果您的解决方案与这些解决方案不完全相同,也不必担心。它们只是作为示例,而非教条。可以下载到目前为止的代码以获得相同的页面。





回页首


数据安全性

在处理 Web 应用程序时,数据安全性的重要性是怎么强调也不过分的。在看似安全但充满风险的商业环境中,必须时刻防范黑客、解密高手、脚本小子、个人身份信息盗窃者、垃圾邮件发送者、网络钓鱼者、犯罪者和单纯的捣乱者。尽管多年以来情况一直都是如此,但数据安全性的重要性依然往往被低估。就算编写出人类历史上最美妙、最优雅的 Web 应用程序也没有多大意义。糟糕的数据安全性将会使应用程序崩溃。要了解如何处理不良数据,应当熟悉一些需要面对的基本问题。

保护数据意味着什么

虽然糟糕的数据安全性会导致很多问题,但是所有问题可以归结为一句话:知道您要应付什么。数据安全性并不意味着要去掉所有 HTML — 尽管您可能希望这样。也不意味着去掉所有特殊字符 — 尽管您可能希望这样。基本原则是应用程序应该知道要应付什么风险。

这不是了解您的愿望。也不是了解应用程序正在请求哪些内容。毫无疑问,这也不是接受一切。

SQL 注入

如果用户能够将 SQL 代码直接传递给应用程序,此代码将在一个查询中执行,那么就可能发生 SQL 注入攻击。例如,用户看到了登录屏幕,在该屏幕中输入用户名和密码。password 变量可能会在以下查询中使用。

"select * from users where username = '" + $username + "' 
and password = '" + $password + "'"

考虑一下,如果用户提交以下密码会发生什么情况:' or '1' = '1。最终的 SQL 语句可能类似以下内容:

"select * from users where username = 'wrestler' 
and password = 'secret' or '1' = '1'"

不检查 SQL 特有的字符可能会给应用程序带来各种隐患。CakePHP 可以帮助您轻松地解决这个问题。

跨站点脚本

跨站点脚本(Cross-site scripting,XSS)代表了一大类攻击,这些攻击的主要目的是把恶意代码显示给没有疑心的用户。通常采用的形式是恶意 JavaScript,从骚扰用户到从 cookie 中捕获数据,它都能做到。

XSS 攻击的核心是提交用户数据后未恰当过滤的应用程序。例如,假定要构建一个包含论坛的应用程序,但是没有对用户数据执行过滤。通过在论坛中发帖提交的任何内容都可以显示。那么假定我是一个恶意用户,通过在论坛中发帖提交了以下文本:

<script>alert("EXPLETIVES!!!")</script>

如果应用程序允许显示此文本,那么查看我帖子的所有人都会收到一个 JavaScript 警告框,向这些人骂脏话。虽然伤害不太大,但您肯定不希望自己的老板看到这种东西。

这是一个无害的 XSS 攻击的简单示例。虽然本例是完全无害的,但 XSS 很可能造成损害。XSS 曾被用于窃取密码、窃取信用卡卡号、伪造新闻等等。保护您自己和您的应用程序免受 XSS 攻击是非常重要的。CakePHP 可以帮助您保护应用程序。

跨站点请求伪造

跨站点请求伪造(Cross-Site Request Forgery,CRSF)可能不像 XSS 一样常见和广为人知,但这并不意味着它不危险。出于演示的目的,我们假定应用程序包含一个论坛,该论坛向主题创建者授予删除主题的权限。在论坛中,已将删除功能实现为仅显示给创建者的按钮,甚至还检验创建者是不是发出请求的人,然后再执行实际的删除操作。通过发布一个名为 action、值为 delete 的字段和一个包含主题的惟一 ID 的 id 字段完成删除操作。查询字符串可能类似于 http://localhost/forum.php?action=delete&id=1729

现在,假定我们发布一张图片,或者具有将图片指定为签名的能力,并提供指向图片的 URL 作为查询字符串。在 HTML 中,代码大致如下:

<img src="http://localhost/forum.php?action=delete&id=1729">

我自己不能直接访问该 URL,因为我不是创建者,应用程序知道这一点。但是,如果主题显示我发布的内容,浏览器就会尝试装入图片,这会请求 http://localhost/forum.php?action=delete&id=1729 — 因为这是创建者发出的请求,所以主题被删除。

这里只非常简单地探讨了 CSRF 及其原理。CakePHP 的 Security 组件可以为您提供保护。





回页首


Sanitize

如果希望要求数据符合某些规则,就应该使用 Sanitize 组件。在 CakePHP 中,输入的所有数据都被正确地转义,这会从根本上防止 SQL 注入攻击。除此之外,CakePHP 在 Sanitize 组件中提供了许多用来清理数据的方法。

Sanitize 是 CakePHP 中帮助您处理数据安全问题的类。与第 2 部分中讨论的 ACL 组件不同,只需在控制器的顶部添加一行代码即可包含 Sanitize 组件。例如,如果需要在 products 控制器中使用 Sanitize,则控制器的顶部可能类似于以下内容:


清单 5. 在产品中使用 Sanitize
                
<?php
App::import('Sanitize');
class ProductsController extends AppController
{
...

Sanitize 提供了四个方法,可以将各种级别的数据安全应用于用户提交的数据。每个方法都用于不同的目的。

Sanitize paranoid 方法

此方法是可用方法中最严格的一个方法。paranoid 方法将去掉字符串中所有不是字母与数字(a-z、A-Z 或 0-9)的字符。paranoid 方法接受一个输入字符串和一个可选的数组($allowedChars)。$allowedChars 数组可用于传递允许使用的字符。例如,如果要求数据是字母、数字、下划线或点号,则使用以下代码:

$clean = Sanitize::paranoid($your_data, array('_','.'));

注意: paranoid 方法将去掉所有空格,除非将 ' ' 作为允许的字符放在 $allowedChars 数组中。

这种数据保护方法被称为白名单(whitelisting)。此方法不是去掉不想要的字符(即黑名单(blacklisting)),而是去掉除明确声明为可接受的字符以外的所有字符。此方法对处理必须遵守特定规则的数据片段(例如用户名、电子邮件地址和密码)很有效,白名单方法可用于几乎所有类型的非二进制数据。

Sanitize html 方法

Sanitize 的 html 方法可以传递两个参数:由 Sanitize 处理的字符串和称为 $remove 的可选布尔标志。

如果 $remove 被设置为 true,则 html 方法将把字符串传递给 PHP 函数 strip_tags,该函数将返回去掉所有 HTML 标记的字符串。例如,strip_tags("<p>Hello</p>", true) 将返回值 Hello

如果 $remove 为 false 或未设置,则 html 方法将用 HTML 实体替换掉一些字符。具体如下:

  • & 被替换为 &
  • % 被替换为 %
  • < 被替换为 <
  • > 被替换为 >
  • " 被替换为 "
  • ' 被替换为 '
  • ( 被替换为 (
  • ) 被替换为 )
  • + 被替换为 +
  • - 被替换为 -

html 方法使用 preg_replace 来执行这些替换操作。

Sanitize 转义方法

转义方法接受一个任意类型的字符串并返回相同字符串的 SQL 安全版本。例如,Sanitize::addslashes("O'Brien") 返回字符串 O\'Brien

Sanitize clean 方法

clean 方法以一个字符串或数组作为输入,并在递归地 “清理” 后返回同一个字符串或数组。清理过程可以解决许多潜在的数据问题,比如处理反斜杠、空白、HTML、回车等等。

可以在 cake/libs/sanitize.php 中查看这些方法以便更好地掌握它们的作用。但是,绝不能修改基本的 CakePHP 文件。这将使升级到新版本变得困难,而且可能会造成异常的行为。





回页首


CakePHP 的 Security 组件

要想使用 CakePHP 的 Security 组件,需要将其添加到控制器的组件中,就像处理 ACL 那样。

var $components = array ('Acl', 'Security');

包含 Security 组件后,就会自动执行很多处理,即使还未使用该组件来保护操作:

  • 通过使用核心 Security 类,生成身份验证密钥并将其写入会话。身份验证密钥将根据 app/config/core.php 中的设置过期。
  • 身份验证密钥在控制器中被设为类变量。
  • 如果控制器生成的任意一个视图使用 $form->create() 来创建表单,则表单中将包含一个名为 _Token/key 的隐藏输入字段,其中包含身份验证密钥。当表单被提交时,系统将把此字段的值与会话中的身份验证密钥值相比较,以检验表单提交是否有效。执行这个检验后,系统将生成一个新的身份验证密钥并在会话中设置它。

现在已经包含了 Security 组件,还需要在使用 Security 组件的控制器中创建 beforeFilter 方法。在执行用户调用的任何方法之前,控制器将自动执行这个方法。这是通常执行安全检查的地方。

有两个方法可以用来实现本文所需的功能。第一个方法要求表单使用 POST 方法。第二个方法要求使用一个有效的身份验证密钥。结合使用这两个方法将为应用程序创建强大的安全基础。

requirePost

Security requirePost 方法让 CakePHP 忽略提交给指定操作的任何信息,除非使用的是 POST。向 requirePost 方法传递一个需要保护的控制器操作列表。例如,如果需要使用 requirePost 保护 delete 方法和 add 方法,则 beforeFilter 方法应当如下所示:

function beforeFilter()
{
  $this->Security->requirePost('delete', 'add');
}

通过要求操作只能使用来自表单 POST 的数据,可以避免攻击者使用查询字符串伪造请求。

requireAuth

Security requireAuth 方法让 CakePHP 用身份验证密钥来检验所有表单提交。仅当通过 POST 提交表单时,才会发生此种检验。

requirePost 方法一样,向 requireAuth 传递一个需要保护的控制器操作列表。例如,如果需要使用 requireAuth 保护 delete 方法和 add 方法,则 beforeFilter 方法应当如下所示:

function beforeFilter()
{
  $this->Security->requireAuth('delete', 'add');
}

要同时使用 requireAuthrequirePost,只需在 beforeFilter 方法中指定它们。


清单 6. 指定 requireAuthrequirePost
                
function beforeFilter()
{
  $this->Security->requireAuth('delete', 'add');
  $this->Security->requirePost('delete', 'add');
}

同时使用 requireAuthrequirePost 保护操作是一个强大的组合。如果需要为不同的操作指定不同的保护级别,甚至可以混合使用这两个方法。


清单 8. 混合使用 requireAuthrequirePost
                
function beforeFilter()
{
  $this->Security->requireAuth('delete');
  $this->Security->requirePost('delete', 'add');
}

通过要求表单提交包含有效的身份验证密钥,然后才能处理,可以使攻击者更难伪造其他用户提交的表单。结合使用 requireAuthrequirePost 是一种非常好的帮助保护应用程序的方法。

浅谈缓存

虽然使用 requireAuth 保护操作有很明显的优点,但是也有一些需要克服的缺点。大多数缺点都属于 “缓存问题”。

每当表单被 requireAuth 评估时,都将生成身份验证密钥。这意味着,如果一个用户提交一个具有已使用过的密钥的表单,则该表单提交操作将被视为无效。在几种情况下会出现这个问题,包括(但不仅限于)使用多个浏览器窗口、使用 Back 按钮返回前一个页面、浏览器缓存、代理服务器缓存等等。您可能会把这些问题看成是 “用户错误”,因此不予理会;但这种想法是不正确的,应该有计划地适当处理无效的表单提交。

提交无效的表单时会发生什么情况?

如果请求被 requirePostrequireAuth 拒绝,则应用程序会退出并向用户发送一个 404 页面。要想修改这种处理方式,可以将 Security 组件的 $blackHoleCallback 属性设置为控制器内的一个函数的名称。例如,如果有一个称为 invalid 的操作和一个相应的视图,则可以让 Security 组件将错误的请求发送给 invalid 操作。可以通过将以下行添加到 beforeFilter 方法的开头来完成这个设置:$this->Security->blackHoleCallback='invalid';

这样就能够控制在用户提交无效请求时的处理方式。用户可能由于合理的原因提交无效请求,例如用户运行应用程序,然后走开了一会儿,当他回来时身份验证密钥已经过期了。





回页首


补充功能

现在您知道了如何使用 Sanitize 组件和 Security 组件,让它们在 Tor 中运行。

首先用 Sanitize 保护所有数据。对于产品、用户和经销商,用 Sanitize 保护提交的所有数据。使用哪一个 Sanitize 方法由您决定。接下来,查看控制器及其操作,判断哪些操作需要使用 requireAuth 来保护,而哪些操作需要使用 requirePost 来保护。相应地实现 Security 组件并使用它。最后,为每个控制器创建一个称为 invalid 的操作,并使用这个方法通知用户提交的表单无效。





回页首


结束语

分享这篇教程……

digg 提交到 Digg
del.icio.us 发布到 del.icio.us
Slashdot Slashdot 一下!

CakePHP 的 Sanitize 组件和 Security 组件并非 灵丹妙药。使用这两个组件并不意味着您可以不再考虑应用程序安全性。但是,使用这两个组件可以使您更轻松地处理一些与安全相关常见的任务。通过清理数据并忽略被错误提交的数据,已经为保护应用程序奠定了良好的基础。

第 4 部分 将主要介绍 CakePHP 的 Session 组件,演示三种保存会话数据的方法,并演示 Request Handler 组件如何帮助您管理各种各样的请求(移动浏览器、包含 XML 或 HTML 的请求等等)。






回页首


下载

描述名字大小下载方法
第 3 部分源代码os-php-cake3.source.zip11KBHTTP
关于下载方法的信息


参考资料

学习

获得产品和技术
  • 使用 IBM 试用软件 改进您的下一个开放源码开发项目,这些软件可以下载或者通过 DVD 获得。

  • 下载 IBM 产品评估版,试用这些来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。


讨论


关于作者

当 Oregon Trail 还仅仅是文字的时候,Duane O'Brien 就已经是一名全能的技术人员了。他最喜欢吃的食物是寿司。他还未曾到过月球。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款