cakephp2.X教程第三部分(基于cakephp1.3.4在线教程的改编)

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

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

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

本文假设您已经学习了  使用 CakePHP 快速打造 Web 站点,第 1 部分:入门    使用 CakePHP 快速打造 Web 站点,第 2 部分:用 CakePHP 打造更大更好的站点 ,而且仍然保留为那两个教程设置的工作环境。如果还没有安装 CakePHP,则应当先简要了解一下第 1 部分和第 2 部分,然后再继续学习本教程。
 

目前为止的 Tor

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

用 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。请考虑以下规则:

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

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

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

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

产品视图增强

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

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

清单 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 所示的内容。

清单 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 保护 delete  add 方法,您的 beforeFilter 方法看起来如 清单 8 所示。

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

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

requireAuth

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

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

清单 9. beforeFilter 方法
<

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

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

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

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

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

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

浅谈缓存

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

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

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

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

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

补充功能

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

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

结束语

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

 

你可能感兴趣的:(cakephp2.X教程第三部分(基于cakephp1.3.4在线教程的改编))