如何使用 Sanitize 和 Security 锁定 CakePHP 应用程序
CakePHP 是一种用 PHP 构建 Web 站点的辅助工具,它稳定、可直接用于生产及快速开发。“使用 CakePHP 快速打造 Web 站点” 系列教程向您展示如何使用 CakePHP 构建在线产品目录。第 3 部分展示如何使用 Sanitize(一个便利的 CakePHP 类),通过清理用户提交的数据帮助确保应用程序的安全性。第 3 部分还介绍 CakePHP 安全组件、处理无效请求和其他高级请求认证。
在 使用 CakePHP 快速打造 Web 站点,第 2 部分:用 CakePHP 打造更大更好的站点 结尾处,您获得了另一个通过增强 Tor 实践您的技能的机会。您可以使用 Bake 为经销商生成视图和控制器,然后检验经销商名称是否是惟一的,修复产品 ACL 中的 bug,更改产品视图使其仅向可以使用 Edit 和 Delete 按钮的用户显示这些按钮。面对如此之多的任务,您是怎样做的?
用 Bake 生成 dealers 控制器和视图应该说是非常简单的。从 /webroot/app 目录中启动 Cake Console。
为了生成控制器,您需要在第一个菜单中输入 C 并选择 dealers 控制器。为了生成视图,您需要在第一个菜单中输入 V 并再次选择 dealers 控制器。这些步骤实际上与创建产品控制器和视图所遵循的步骤是相同的。
还必须确保经销商名称是惟一的。实现的方法是在 Dealer 模型中添加一个 beforeValidate 方法,如清单 1 所示。
<?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 条目,这是必须的。没有这些条目的话,如果您想创建一个新经销商且尝试使用该经销商添加一个新产品,将会遇到一些问题。
另一个任务是修复 products add 方法中的 bug。正如 使用 CakePHP 快速打造 Web 站点,第 2 部分:用 CakePHP 打造更大更好的站点 结尾处所述,任何人都可以添加一个产品,即使是在用户注销后。要解决此问题有多种方法。
您可以使用访问控制列表(Access Control List,ACL)在 products 控制器中创建一个操作,并使用它为每个经销商 ACO 上的用户访问请求对象(ARO)授权 create 访问权限。然后删除此操作,因为不再需要使用它。接下来,修改 dealer add 操作,将经销商 ACO 的 create 权限授予用户的 ARO。最后,在 products add 方法中进行检查以确保用户具有权限。
但是考虑到手边的任务,有一个更简单的方法,如清单 2 所示。
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。请考虑以下规则:
在应用程序中还可以更进一步,比如:
可以使用多种其他方法来完成此任务。体验其中的几种。要勇于动手实践!
需要完成的最后一项任务是修改产品视图,使 Edit 和 Delete 按钮仅对可以编辑或删除该产品的用户显示。首先,您应该从 Products 索引视图中完全删除 Edit 和 Delete按钮。相反,您可以检查每个拥有用户权限的产品,以及在索引视图中隐藏或显示这个按钮,但是这个解决方案可能无法令您满意。
在 Products 视图操作中,您可以查看用户是否有更新或删除产品的权限,并设置用于显示或隐藏视图中按钮的变量。在控制器的 view 操作中,应当有清单 3 所示的代码。
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 所示的内容。
<?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 注入攻击。例如,用户面前出现了一个登录屏幕,在该屏幕中输入用户名和密码。password 变量可能会在清单 5 中的查询中使用。
"select * from users where username = '" + $username + "' and password = '" + $password + "'"
考虑一下,如果用户提交以下密码会发生什么情况:' or '1' = '1。最终的 SQL 语句如清单 6 所示:
"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 组件。在 Cake 中,所有输入数据都被正确地转义,这会从根本上防止 SQL 注入攻击。除此之外,Cake 在 Sanitize 组件中提供了许多方法可以清理您的数据。
Sanitize 是 CakePHP 类,可以帮助您解决数据安全问题。与第 2 部分中讨论的 ACL 组件不同,只在控制器的顶部添加一行代码即可包含 Sanitize 组件。例如,如果您想要在您的产品控制器中使用 Sanitize,则控制器的顶部看起来如清单 7 所示。
<?php App::import('Sanitize'); class ProductsController extends AppController { ...
Sanitize 提供了 4 个方法,可以将各种级别的数据安全性应用于用户提交的数据。每个方法都有不同的目的。
这是所有方法中最严格的一个。paranoid 方法将去掉字符串中所有不是字母与数字(a-z、A-Z 或 0-9)的字符。paranoid 方法接受一个输入字符串和一个可选的数组($allowedChars)。$allowedChars 数组可用于传递允许使用的字符。例如,如果要求数据是字母、数字、下划线或点号,则使用以下代码:
$clean = Sanitize::paranoid($your_data, array('_','.'));
注意:paranoid 方法将去掉所有空格,除非将 ' '(空格放在引号中)作为允许的字符放在 $allowedChars 数组中。
这种数据保护方法被称为 白名单(whitelisting)。此方法不是去掉不想要的字符(即黑名单(blacklisting)),而是去掉除明确声明为可接受的字符以外的所有字符。此方法对处理必须遵守特定规则的数据片段(例如用户名、电子邮件地址和密码)很有效,但白名单方法可用于几乎所有类型的非二进制数据。
Sanitize 的 html 方法可以传递两个参数:由 Sanitize 处理的字符串和一组选项。不管您传递哪个选项,该方法将字符串传递给 PHP 函数htmlentities,然后返回结果。您可以传递给 html 方法的选项包括:
escape 方法接受一个任意类型的字符串并返回相同字符串的 SQL 安全版本。例如,Sanitize::escape("O'Brien") 返回一个值为 O\'Brien的字符串。
clean 方法以一个字符串或数组作为输入,并在递归地 “清理” 后返回同一个字符串或数组。清理过程可以解决许多潜在的数据问题,比如处理棘手的反斜杠、离奇的空格、HTML、回车等等。
可以在 cake/libs/sanitize.php 中查看这些方法的代码以便更好地处理它所涉及的内容。但是,绝不能修改基本的 CakePHP 文件。这将使升级到新版本变得困难,而且可能会造成异常行为。
Cake 的 Sanitize 类使得清理您的数据变得很容易。您应该使用它所提供的功能来帮助锁定您的应用程序。现在让我们来看一个 Cake 组件,可以帮助锁定更多内容:Security 组件。
回页首
要想使用 CakePHP 的 Security 组件,需要将其添加到控制器的组件中,就像处理 ACL 那样: var $components = array ('Acl', 'Security');。
包含 Security 组件后,就会自动执行很多处理,即使还未使用该组件来保护操作:
现在已经包含了 Security 组件,您还需要在使用 Security 组件的控制器中创建 beforeFilter 方法。在执行用户调用的任何方法之前,控制器将自动执行这个方法。这是通常执行安全检查的地方。
有两个方法可以用来实现 Security 组件所需的功能。第一个方法要求表单使用 POST 方法。第二个方法要求使用一个有效的认证密钥。结合使用这两种方法将为您的应用程序创建强大的安全基础。
requirePost 方法通知 CakePHP 忽略提交给指定操作的任何信息,除非使用的是 POST。向 requirePost 方法传递一个需要保护的控制器操作列表。例如,如果需要使用 requirePost 保护 delete 和 add 方法,您的 beforeFilter 方法看起来如 清单 8 所示。
function beforeFilter() { $this->Security->requirePost('delete', 'add'); }
通过要求操作只能使用来自表单发送的数据,您可以避免有人使用查询字符串伪造请求。
Security 的 requireAuth 方法通知 CakePHP 用之前提到的认证密钥来检验所有表单提交。仅当通过 POST 提交表单时,才会发生此种验证。
与 requirePost 方法一样,向 requireAuth 传递一个需要保护的控制器操作列表。例如,如果需要使用 requireAuth 保护 delete 和 add 方法,您的 beforeFilter 方法如清单 9 所示。
< function beforeFilter() { $this->Security->requireAuth('delete', 'add'); }
要使用 requireAuth 和 requirePost,只需在 beforeFilter 方法(见清单 10)中指定它们。
function beforeFilter() { $this->Security->requireAuth('delete', 'add'); $this->Security->requirePost('delete', 'add'); }
使用 requireAuth 和 requirePost 保护操作是一个功能强大的组合。如果您想改变不同方法的保护级别,需要混合搭配使用它们(见清单 11)。
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 组件并非 灵丹妙药。使用这两个组件并不意味着您可以不再考虑应用程序安全性。但是,使用这两个组件可以使您更轻松地处理一些与安全相关常见的任务。通过清理数据并忽略被错误提交的数据,已经为保护应用程序奠定了良好的基础。