2012-08-26

几种极其隐蔽的XSS注入的防护

Views: 19491 | 5 Comments

XSS注入的本质就是: 网页中根据用户的输入, 不期待地生成了可执行的js代码, 并且js得到了浏览器的执行. 意思是说, 服务器响应给浏览器的字符串中, 包含了一段非法的js代码, 而这段代码跟用户的输入有关.

常见的XSS注入防护, 可以通过简单的 htmlspecialchars(转义HTML特殊字符), strip_tags(清除HTML标签) 来解决, 但是, 还有一些隐蔽的XSS注入不能通过这两个方法来解决, 而且, 有时业务需要不允许清除HTML标签和特殊字符. 下面列举几种隐蔽的XSS注入方法:

IE6/7 UTF7 XSS 漏洞攻击

隐蔽指数: 5
伤害指数: 4

这个漏洞非常隐蔽, 因为它让出现漏洞的网页看起来只有英文字母(ASCII字符), 并没有非法字符, htmlspecialchars 和 strip_tags 函数对这种攻击没有作用. 不过, 这个攻击只对 IE6/IE7 起作用, 从 IE8 起微软已经修复了. 你可以把下面这段代码保存到一个文本文件中(前面不要有空格和换行), 然后用 IE6 打开试试(没有恶意代码, 只是一个演示):

+/v8 +ADw-script+AD4-alert(document.location)+ADw-/script+AD4-

最容易中招的就是 JSONP 的应用了, 解决方法是把非字母和数字下划线的字符全部过滤掉. 还有一种方法是在网页开始输出空格或者换行, 这样, UTF7-XSS 就不能起作用了.

因为只对非常老版本的 IE6/IE7 造成伤害, 对 Firefox/Chrome 没有伤害, 所以伤害指数只能给 4 颗星.

参考资料: UTF7-XSS

JSONP函数名注入

隐蔽指数: 5
伤害指数: 5

JSONP是用于解决跨域问题的最主要的方法. 但是, 由于JSONP的回调(callback)函数名是由客户端传递的, 所以极有可能被注入.

服务器的逻辑一般是这样:

echo $_GET['callback] . '(' . json_encode($data) . ');';

很明显, 这段代码会被XSS注入. 有一种传播非常广泛的错误的解决方法, 也就是给callback进行HTML转义:

echo htmlspecialchars($_GET['callback]) . '(' . json_encode($data) . ');';

咋一看以为是解决了, 事实仍然可能被"UTF7 XSS 漏洞攻击"! 隐蔽性极高, 5颗星! 危害所有的主流浏览器.

正确的处理方式

1. 正则过滤:

$callback = preg_replace('/[^a-z0-9\._\$]/i', '', $callback);

2. 设置正确的 HTTP Content-Type

header('Content-Type: text/javascript');

此方法要求浏览器把服务器的输出解释成JavaScript代码, 所以HTML标签就不会起作用了. 但是, 在某些浏览器下调试时会提示用户下载文件, 非常不方便.

不正确地拼接 JavaScript/JSON 代码段

隐蔽指数: 5
伤害指数: 5

Web 前端程序员经常在 PHP 代码或者某些模板语言中, 动态地生成一些 JavaScript 代码片段, 例如最常见的:

var a = '<?php echo htmlspecialchars($name); ?>';

不想, $name 是通过用户输入的, 当用户输入 a'; alert(1); 时, 就形成了非法的 JavaScript 代码, 也就是 XSS 注入了.

在解决问题之前, 我们要思考问题的本质是什么? 本质在于程序员可以用字符串来控制整个世界, 却没有用正确的方法来生成正确的字符串, 而是采用了功能强大且原始的"手工字符串拼接".

只需要把上面的代码改成:

var a = <?php echo json_encode($name); ?>;

去掉单引号, 利用 PHP 的 json_encode() 函数来生成表示字符串的字符串. 这样做是因为, 最好用 json_encode() 函数来生成所有的 JSON 串, 而不要试图自己去拼接. 程序员总是犯这样的错误: 自己去解析 HTTP 报文, 而不是用现成的成熟的库来解析. 用 json_encode() 的好处还在于, 即使业务要求"我要保留单引号"时, XSS注入也可以避免.

隐蔽指数最高级, 伤害所有的通用浏览器. 这种 XSS 注入方式具有非常重要的参考意义.

讨论:

最后, 根据工作中的经验, 以及我自己和别人犯过的错, 我总结出一个定理: 没有一劳永逸的单一方法可以解决所有 XSS 注入问题.

有用的经验:

  1. 输出 HTML 代码时 htmlspecialchars
  2. 输出 JavaScript 代码时 json_encode
  3. JavaScript 函数名(JSONP)用正则过滤
  4. 输入过滤应该用于解决业务限制, 而不是用于解决 XSS 注入(与严进宽出的原则相悖, 所以本条值得讨论)

上文提到的经验第3条, 是一种"宽进严出"的原则, 和"严进宽出"原则是相悖的. 其实, 我认为不应该把"严进宽出"作为一条伪真理, 好像除了它其它的说法都不对了似的. "宽进严出"和"严进宽出"应该具有完全相等的地位, 根据实现的成本进行取舍.

例如, 用户的名字可以采用"严进宽出"原则, 不允许用户填写单引号, 大于号小于号等. 但是用户的签名呢? 难道就不能填单引号? 如果要走极端, 想找出一种银弹, 那么我能想到的就是对所有的输入一律进行 htmlspecialchars 和 json_encode(且不说解决不了 utf7-xss).

其实, XSS 注入的解决应该是和输出端相关的. 当需要输出到文本文件时, 过滤和转义都是无必要的. 当输出到 HTML 渲染引擎时, json_encode 是无必要的. 当输出到 JS 引擎时, htmlspecialchars 是无必要的.

Related posts:

  1. 如何让 PHP json_encode 函数不转义中文?
  2. 以浏览器为核心的客户端软件的安全问题
  3. JavaScript+jQuery两栏选择控件
  4. PHP浮点数显示和转成字符串
  5. 强大的纯JS数据图工具-flot
Posted by ideawu at 2012-08-26 17:37:46

5 Responses to "几种极其隐蔽的XSS注入的防护"

  • 昨天是这么解决的:
    //设置响应头的content-type
    header(‘Content-Type: application/javascript;charset=utf-8′);
    //转义,响应加空格
    $request->callback = " " . htmlspecialchars($request->callback); Reply
  • 赞,眼拙第二个看成a标签 Reply
  • 文中两处错误,
    1,正则$callback = preg_replace(‘/^[a-z0-9\._\$]+$/i’, ”, $callback),应该是$callback = preg_replace(‘/[^a-z0-9\._\$]+$/i’, ”, $callback)
    2, a’; alert(1);应该是 a’; <script>alert(1);</script> Reply
    @noahlu: 呵呵. 第1条已经更正了, 原来这段正则是用来做preg_match(), 不注意没改动就直接搬过来了. 第2条, 不需要加script标签, 因为那段代码是JavaScript代码. Reply
  • 现在web程序上的xss也不多见了,但是一旦触发就可能cookie绕过密码。还是很危险的。 Reply

Leave a Comment