编写安全的代码

几乎每天我们都可以看到关于这种或者那种软件具有安全漏洞的头条消息。对于每个严禁的开发者来讲将恶意用户拒之门外是头等大事。

一个带有恶意的用户可以使用多种方式来攻击你的 Drupal 站点。这些攻击方式中包括,向你的系统中注入代码并让它执行,操纵你数据库中的数据,查看用户无权访问的资料,通过你的 Drupal 站点发送垃圾邮件。在本章,我们将学习如何编写安全的代码来阻止这些攻击。

幸运的是,Drupal 提供了一些工具用来很容易的消除这些常见的安全漏洞。

处理用户输入

当用户与 Drupal 交互时,一般都是通过一系列的表单比如节点提交表单、评论提交表单来完成的。用户也可能使用 blogapi module 来发布一个基于 Drupal 的日志。Drupal 的用户输入方式可以总结为“存储原始的;过滤输出的”(store the original; filter on output)。数据库中总应该保存一份与用户输入完全一样的内容。当用户输入的内容准备用来生成 web 页面输出时,应该进行过滤。

当用户输入的内容在你的程序中被执行时,就可能引起安全漏洞。当你编写你的程序时,如果你没有全面的考虑各种情况的话,就会留下安全隐患。你可能期 望用户输入标准的字符,而事实上他们可以输入非标准的字符串,比如控制字符。你可能看到其中包含字符 %20 的URL;例如,http://example.com/my%20document.html。这是一个为了与 URL 规范(参看 http://www.w3.org/Addressing/URL/url-spec.html)兼容对空格字符编码后的结果。当有人保存了一个名为 my document.html 的文件并且可从 web 服务器上请求它,那么就会对空格编码。% 意味着编码了的字符,而20则因为这它是 ASCII 的第20个字符。恶意用户可通过花招来使用编码字符给你的网站带来麻烦,你将会在本章的后面看到这一点。

考虑数据类型

当在一个像 Drupal 这样的系统中处理文本时,由于用户输入将会作为站点的一部分展示出来,所以将用户输入看做一个带有类型的变量能帮我们很好的理解这个系统。如果你使用过强 类型语言比如 JAVA 变过程序,那么你将熟悉强类型变量。例如,在 Java 中一个整数就是一个整数。在 PHP(弱类型语言)中,使用 PHP 的自动类型转换,根据上下文,你既可以把整数看做字符串也可以看做整数。但是优秀的 PHP 程序员都会仔细的考虑类型并恰到好处的利用自动类型转换。同样,尽管是通过用户输入得到的,节点提交表单中的“Body(主体)”字段,也可以作为一个文 本进行处理,如果我们把它看做具有特定类型的文本,那么就会更好的理解的它的本质。用户输入的是纯文本么?用户输入的文本中是否带有 HTML 标签,如果带有的话是否将它们也一同显示出来?如果带有 HTML 标签的话,这些标签中是否允许带有恶意的标签,比如 JavaScript,它可以将你的页面替换成一个手机铃声的广告?展示给用户的页面处在 HTML 格式中;用户输入是各种文本格式类型的变体,在展示它们以前必须安全的将其转化为 HTML。如果我们使用这种方式来考虑用户输入的话,这能够帮助我们理解 Drupal 的文本转换功能的工作原理。文本输入的常见类型,还有将文本转化为另一种格式的函数,如表20-1所示。

表20-1 将一种文本类型安全的转化为另一种类型

Source Format - Target Format - Drupal Function - What It Does

Plain text - HTML - check_plain() - Encodes special characters into HTML entities

HTML text - HTML - filter_xss() - Checks and cleans HTML using a tag whitelist

Rich text - HTML - check_markup() - Runs text through filters

Plain text - URL - drupal_urlencode() - Encodes special characters into %0x

URL - HTML - check_url() - Strips out harmful protocols, such as javascript:

Plain text - MIME - mime_header_encode() - Encodes non-ASCII, UTF-8 encoded characters

Plain text 是仅仅包含纯文本的文本。例如,如果你让一个用户在一个表单中键入他/她喜欢的颜色,你期望用户输入“green(绿色)”或者 “purple(紫色)”,而不包含任何标识字体。在另一个网页中包含这个输入框而不进行任何检查来确保它真的仅仅包含纯文本,那么就会留下安全漏洞。例 如用户没有输入一个颜色,而输入了一下内容:

http://evil.example.com/133/index.php?s=11&;ce_cid=38181161′”>

因此,我们可以使用 check_plain() 来确保通过将 HTML 标签转义为 HTML 实体来消除潜在的危害。从 check_plain() 返回的文本不包含任何 HTML 标签,因为将它们转换为相应的实体了。

<img src=”javascript:window.location =’<a href=”http://evil.example.com/133/index.php?s=11&”>http://evil.example.com/133/index.php?s=11&</a>;ce_cid=38181161′”>

HTML text 可以包含 HTML 标识字体。然而,你永远不要盲目的相信用户仅输入安全的 HTML;一般情况下,你想将用户可用的标签限制在一个特定的有限集中。例如, 一般都会被你禁用,因为它允许用户在你的站点上运行他们选择的脚本。同样,你也不想让用户使用 标签在你的站点上建立表单。

Rich text 是比纯文本包含更多信息的文本,它不一定必须是 HTML。它可以包含 wiki 标识字体,或者论坛代码(BBCode),或者其它的标识语言。在展示以前,必须使用一个过滤器来将这些文本转化为 HTML。

URL 是由用户输入或者其它不可信的地方获取的 URL。你可能希望用户输入 http://example.com,但是用户却输入了 javascript:runevilJS()。在将 URL 展现在 HTML 页面以前,你必须使用 check_url() 来检查它以确保它的格式良好并且不包含任何攻击。

注意:更过关于过滤器的信息,参看第11章。

使用 check_plain() 和 t()

当你对你用到的文本不信任,并且你不想在文本中有任何 markup(标识字体)时,使用 check_plain()。

下面是使用用户输入的原始方式,假定用户刚刚在一个文本输入框中输入了一个喜欢的颜色。

下面的代码不安全:

drupal_set_message(”Your favorite color is $color!”); // No input checking!

下面的代码安全,但不是最佳实践:

drupal_set_message(’Your favorite color is ‘ . check_plain($color));

它是坏的代码,这是因为没有把这个文本字符串放到 t() 中,对于文本字符串总要调用该函数的。如果你像上面这样编写代码,那你就等着挨骂吧,翻译者将不能够翻译你的语句,因为它没有使用 t() 函数。

你不能将变量直接放到双引号中,并将它们传递给 t()。下面的代码仍然不安全,因为没有使用占位符:

drupal_set_message(t(”Your favorite color is $color!”)); // No input checking!

t() 函数提供了一种内置的方式用来确保你的字符串的安全性,使用一个带有单字符前缀的占位符,如下所示。

下面的代码很安全并且格式良好:

drupal_set_message(t(’Your favorite color is @color’, array(’@color’ => $color));

注意数组中的键(@color)与字符串中的占位符完全相同。

消息的结果如同下面的这样:

Your favorite color is brown.

前缀 @ 告诉 t() 对替换占位符的值调用 check_plain()。

注意:当运行一个 Drupal 的 t() 时,将对占位符的值调用 check_plain(),对于其它的字符串则不调用 check_plain()。所以你需要信任你的翻译者。

在这里,我们可能想通过改变颜色值的样式来强调用户所选择的颜色。使用 % 前缀可以达成所愿,它意味着“对于该值执行 theme(’placeholder’, $value)”。它间接的将值传递给了 check_plain(),如图20-1所示。前缀 % 是最常用的前缀。

下面的代码是安全的并且格式良好:

drupal_set_message(t(’Your favorite color is %color’, array(’%color’ => $color));

一个消息产生的结果如下所示。使用 theme_placeholder() 对该值进行了主题化,它简单的使用了 标签对值进行了包装。

Your favorite color is brown.

如果你的文本在前面已被清理过了,你可以使用前缀 ! 来禁用 t() 中的检查,然而如果你可以避免它的话,并不推荐这么做:

// l() function runs text through check_plain() and returns sanitized text
// so no need for us to do check_plain($link) or to have t() do it for us.
$link = l($user_supplied_text, $user_supplied_path);
drupal_set_message(t(’Go to the website !website’, array(’!website’ => $link));

在 t() 中的用于字符串替换的 @、% 和 ! 占位符的作用,如图20-1所示。在该图中我们是用了一个简单的例子进行说明,记住你可以在字符串中使用多个占位符并将它们添加到数组中。

图20-1 在字符串替换时,不同占位符前缀所起的作用

相关文章


没有评论

(*)
(不会公布)