HTML Entities 转换问题

用 Python 给富文本编辑器的后端做一个 HTMLPurifier 组件。各种 XSS 技巧都筛了一遍。觉得万无一失了,没想到还是栽在了坑里。

HTMLPurifier 组件使用 Python HTMLParser 来实现。HTMLParser 在处理 HTML Entities 的时候和主流的浏览器不一致。主流浏览器(FireFox、Chrome、IE)都允许省略HTML Entities末尾的分号。而 HTMLParser 的unescape 函数在处理时要求必须有结尾分号,导致后端的 HTML 过滤被绕过。

如下代码:

HTML<a href="javascript&#58alert()">XSS</a>

对于 HTMLParser 来说,&#58 并不是一个正确的 HTML Entity,所以不会进行任何处理,链接被识别为 URL 路径,而不是 JavaScript 调用协议。但是主流浏览器会将&#58 解析成 :,链接被识别为 JavaScript 调用协议。渲染后的 HTML 代码成了:

HTML<a href="javascript:alert()">XSS</a>

不单单是 Python 的 HTMLParser 对 HTML Entities 的处理方式和主流浏览器不一致,PHP 的 html_entity_decode() 函数也存在同样的问题。

另外对于 &nbsp;&amp;&colon;,Chrome 的处理方法竟然不一致:

  1. &nbspTEXT 解析为 TEXT&nbsp 转换成了空格;
  2. &ampTEXT 解析为 &TEXT&amp 转换成了 &
  3. &colonTEXT 解析为 &colonTEXT&colon 没能转换成 :