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

如何使用 Sanitize 和 Security 锁定 CakePHP 应用程序

CakePHP 是一种用 PHP 构建 Web 站点的辅助工具,它稳定、可直接用于生产及快速开发。“使用 CakePHP 快速打造 Web 站点” 系列教程向您展示如何使用 CakePHP 构建在线产品目录。第 3 部分展示如何使用 Sanitize(一个便利的 CakePHP 类),通过清理用户提交的数据帮助确保应用程序的安全性。第 3 部分还介绍 CakePHP 安全组件、处理无效请求和其他高级请求认证。

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

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



2011 年 3 月 17 日 (最初于 2006 年 12 月 19 日)

编者按:本系列最初发表于 2006 年,为了跟上不断发展的 CakePHP,经历了多次修改。该修改版用的是 V1.3.4。

使用 CakePHP 快速打造 Web 站点” 系列教程是专为应用程序开发人员而设计的,使用 CakePHP 可以使他们的生活变得轻松。通过本系列您将学习如何安装和配置 CakePHP 以及 Model-View-Controller(MVC)设计基本概念、如何在 CakePHP 中检验用户数据、如何使用 CakePHP 帮助函数、如何使用 CakePHP 快速建立并运行应用程序。听起来好像有很多东西要学习,但不必担心 — CakePHP 会替您完成其中的大部分工作。

本文假设您已经学习了 使用 CakePHP 快速打造 Web 站点,第 1 部分:入门使用 CakePHP 快速打造 Web 站点,第 2 部分:用 CakePHP 打造更大更好的站点,而且仍然保留为那两个教程设置的工作环境。如果还没有安装 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.1。
  3. 一个受支持的数据库引擎。本教程采用的是 MySQL V5.0.67。

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

下载 CakePHP 的最简单方法是访问 CakeForge.org 并从 Downloads 小节下载最新的稳定版本。本教程采用的是 V1.3.4。(见 参考资料)。


目前为止的 Tor

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

用 Bake 生成经销商

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

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

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

清单 1. Dealer 模型
<?php
class Dealer extends AppModel
{
  var $name = 'Dealer';
  var $hasMany = array ('Product' => array('className' => 'Product',
'foreignKey'=>'dealer_id'));
  var $actsAs = array('Acl' => array('type' => 'controlled'));

  function beforeValidate() {
      if (!$this->id) {
      if ($this->findByTitle($this->data['Dealer']['title'])) {
      $this->invalidate('title_unique');
      return false;
      }
      }
      return true;
   }

  function parentNode() {
      return null;
  }
}
?>

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

如果您对本系列比较关注的话,您可能会注意到我们添加了一个 $actsAs 行和一个 parentNode 方法。为了为经销商创建 aco 条目,这是必须的。没有这些条目的话,如果您想创建一个新经销商且尝试使用该经销商添加一个新产品,将会遇到一些问题。

添加产品 bug 修复

另一个任务是修复 products add 方法中的 bug。正如 使用 CakePHP 快速打造 Web 站点,第 2 部分:用 CakePHP 打造更大更好的站点 结尾处所述,任何人都可以添加一个产品,即使是在用户注销后。要解决此问题有多种方法。

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

但是考虑到手边的任务,有一个更简单的方法,如清单 2 所示。

清单 2. 在用户的 add 操作中检查用户是否已登录
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。请考虑以下规则:

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

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

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

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

产品视图增强

需要完成的最后一项任务是修改产品视图,使 EditDelete 按钮仅对可以编辑或删除该产品的用户显示。首先,您应该从 Products 索引视图中完全删除 EditDelete按钮。相反,您可以检查每个拥有用户权限的产品,以及在索引视图中隐藏或显示这个按钮,但是这个解决方案可能无法令您满意。

在 Products 视图操作中,您可以查看用户是否有更新或删除产品的权限,并设置用于显示或隐藏视图中按钮的变量。在控制器的 view 操作中,应当有清单 3 所示的代码。

清单 3. 可行的 view 操作解决方案
if (@$this->Acl->check(
        array('model' => 'User', 'foreign_key' => (int) $this->Session->
read('user_id')),
        array('model' => 'Product', 'foreign_key' => $product['Product']['id']),
        'update')
   ) {
        $this->set('showUpdate', TRUE);
   } else {
         $this->set('showUpdate', FALSE);
   }

if (@$this->Acl->check(
         array('model' => 'User', 'foreign_key' => (int) $this->Session->
read('user_id')),
         array('model' => 'Product', 'foreign_key' => $product['Product']['id']),
                    'delete')
   ) {
         $this->set('showDelete', TRUE);
   } else {
         $this->set('showDelete', FALSE);
   }

另外,在 products/view.ctp 中,需要清单 4 所示的内容。

清单 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 } ?>

另外,如果您的解决方案不能完全符合这些解决方案,也不必担心。它们只是作为示例,而非教条。您可以下载第 3 部分的 源代码,这样我们就都是在同一个页面上。


数据安全性

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

保护数据意味着什么

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

这和您想要知道什么是不一样的,也和应用程序要请求什么是不一样的。而且,它的确和接受所有一切不是同一件事。

SQL 注入

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

清单 5. 使用 password 变量的 SQL 语句
"select * from users where username = '" + $username + "' 
and password = '" + $password + "'"

考虑一下,如果用户提交以下密码会发生什么情况:' or '1' = '1。最终的 SQL 语句如清单 6 所示:

清单 6. 最终的 SQL 语句
"select * from users where username = 'zaphod' 
and password = 'secret' or '1' = '1'"

不检查 SQL 特定的字符可能会打开您的应用程序,带来各种漏洞隐患。恰当地使用,Cake 可以轻松地保护您的应用程序免遭这类漏洞攻击。

跨站点脚本

跨站点脚本(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 组件。在 Cake 中,所有输入数据都被正确地转义,这会从根本上防止 SQL 注入攻击。除此之外,Cake 在 Sanitize 组件中提供了许多方法可以清理您的数据。

Sanitize 是 CakePHP 类,可以帮助您解决数据安全问题。与第 2 部分中讨论的 ACL 组件不同,只在控制器的顶部添加一行代码即可包含 Sanitize 组件。例如,如果您想要在您的产品控制器中使用 Sanitize,则控制器的顶部看起来如清单 7 所示。

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

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

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 处理的字符串和一组选项。不管您传递哪个选项,该方法将字符串传递给 PHP 函数 htmlentities,然后返回结果。您可以传递给 html 方法的选项包括:

  • remove— 如果被设置为 true,字符串将被传递到 PHP 函数 strip_tags 来去掉任何 HTML。
  • charset— 如果您为字符集传递一个值,该值将被传递到 htmlentities 函数,如果您没有为字符集传递一个值,Cake 将尝试为该选项确定一个正确值,而且不会将传递给 htmlentities 函数。
  • quotes— 这个参数的有效值是 ENT_COMPAT(转换双引号,忽略单引号),ENT_QUOTES(转换双引号和单引号),而 ENT_NOQUOTES(既不转换双引号也不转换单引号)。如果您想传递一个值,它将默认为 ENT_QUOTES。该值然后被传递到 htmlentities 函数。

Sanitize escape 方法

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

Sanitize clean 方法

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

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

Cake 的 Sanitize 类使得清理您的数据变得很容易。您应该使用它所提供的功能来帮助锁定您的应用程序。现在让我们来看一个 Cake 组件,可以帮助锁定更多内容:Security 组件。


CakePHP 的 Security 组件

要想使用 CakePHP 的 Security 组件,需要将其添加到控制器的组件中,就像处理 ACL 那样: var $components = array ('Acl', 'Security');

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

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

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

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

requirePost

requirePost 方法通知 CakePHP 忽略提交给指定操作的任何信息,除非使用的是 POST。向 requirePost 方法传递一个需要保护的控制器操作列表。例如,如果需要使用 requirePost 保护 deleteadd 方法,您的 beforeFilter 方法看起来如 清单 8 所示。

清单 8. 使用 requirePost 保护 deleteadd 方法
function beforeFilter()
{
  $this->Security->requirePost('delete', 'add');
}

通过要求操作只能使用来自表单发送的数据,您可以避免有人使用查询字符串伪造请求。

requireAuth

Security 的 requireAuth 方法通知 CakePHP 用之前提到的认证密钥来检验所有表单提交。仅当通过 POST 提交表单时,才会发生此种验证。

requirePost 方法一样,向 requireAuth 传递一个需要保护的控制器操作列表。例如,如果需要使用 requireAuth 保护 deleteadd 方法,您的 beforeFilter 方法如清单 9 所示。

清单 9. beforeFilter 方法
<

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

要使用 requireAuthrequirePost,只需在 beforeFilter 方法(见清单 10)中指定它们。

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

使用 requireAuthrequirePost 保护操作是一个功能强大的组合。如果您想改变不同方法的保护级别,需要混合搭配使用它们(见清单 11)。

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

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

浅谈缓存

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

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

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

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

倘若一个用户想要通过合法手段提交无效请求,这可以给您一些显示控制。这方面的一个很好的例子是用户正在运行应用程序,午饭过后,当他返回到应用程序时,认证密钥已经过期了。


补充功能

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

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


结束语

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

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


下载

描述名字大小
第 3 部分源代码os-php-cake3.source.zip11KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source
ArticleID=220964
ArticleTitle=使用 CakePHP 快速打造 web 站点,第 3 部分: 使用 Sanitize 进行保护
publish-date=03172011