关于 XSS 防范的一些思考
2016 2 25 04:35 AM 2993次查看
分类:Web 标签:HTML, JavaScript
于是在翻了一堆资料后,觉得还是把自己对它的一些思考记录下来吧。
先要说明的是,不同的地方,有不同的方式避免 XSS:
- HTML 标签的文本部分,例如:
如果 user_data 里包含了 HTML 标签,那么展示的外观(可以加个 img 标签)和逻辑(可以加个 script 标签)都可能被篡改。所以这里至少要把<div>{user_input}</div>
<
字符转义成<
,也就不会开启和关闭任何标签了。
单独的>
字符并不会关闭标签,所以它可以不处理。但是输出成 XML(如 RSS)或 XHTML 格式时,单独的>
会造成页面解析失败,所以这种情况下需要转义。
此外,用户在输入&
的时候,是希望它显示成&
的,而结果却显示成了&
,这也是不符合预期的。所以&
字符也应该被转义成&
。
另一个小问题是连续的空格和回车可能都被当成了一个空格,这可以用white-space: pre-wrap
的样式来处理,也可以替换成
和<br>
,或者直接不管。
综上,这里的解决方案是至少转义<
和&
字符:def escape_html_text(string): return string.replace('&', '&').replace('<', '<')
function escape_html_text(string) { return string.replace('&', '&').replace('<', '<'); }
- HTML 标签的属性部分,例如:
这里自然也不能允许用户输入<input value="{user_input}">
<
和&
字符。
此外,如果能输入>
的话,就可以轻松地关闭 input 标签了,所以>
需要被转义成>
。
而如果允许输入引号的话,这个属性就能被关闭,然后用户可以接着插入其他属性,所以"
和'
需要被转义成"
和'
('
是 XML 里的 entity,在 HTML 里没有定义,所以更推荐用前者)。
此外,IE 允许用`
字符作为属性的分割符:
所以有需要的话,也可以转义 ` 字符。但如果你能保证你的的代码里只使用<input value=`{user_input}`>
"
和'
作为属性分割符,那就可以不处理。
另一个更重要的事是属性一定要用引号包围起来,虽然写成下面的格式也是可以的,但很难保证不被 XSS 攻击:
如果非要这么做的话,OWASP 建议将属性值中所有的 ASCII 字符都编码成<input value={user_input}>
&#xHH;
的形式。
回到我们的解决方案,考虑到需要替换的次数太多,所以改用正则表达式效率更高些:import re escape_pattern = re.compile(r'[&<>"\']') escape_map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' } def replacer(match): return escape_map[match.group(0)] def escape_html_attr(string): return escape_pattern.sub(replacer, string)
其实,Python 也可以直接用 cgi.escape 函数,至少我懒得写这些代码。var escape_map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; function replacer(char) { return escape_map[char]; } function escape_html_attr(string) { return string.replace(/[&<>"']/g, replacer); }
另外,这种替换也适用于 HTML 标签的文本部分,只是输出的字节数可能会变多而已,没别的副作用。 - HTML 标签的网址属性部分,例如:
它们的处理规则其实并不一样:<img src="{user_input}"> <a href="{user_input}">{user_input}</a> <button onclick="{user_input}"> <link ref="{user_input}" href="{user_input}"> <script src="{user_input}">
- 图片的网址需要对 HTML 属性做编码。
- 链接的地址需要判断是否是合法的网址(最好是用户输入时就提示),否则用户可以输入
javascript:alert(0)
之类的代码。 - 后面几个应该不允许用户输入。
- 图片的网址需要对 HTML 属性做编码。
- URL 的参数部分,例如:
需要对参数进行百分号编码(percent-encoding),JavaScript 有内置的<a href="/path?key={user_input}">...</a>
encodeURIComponent
函数(会忽略字母、数字和- _ . ! ~ * ' ( )
这些字符),Python 可以用urllib.quote(url, '-_.!~*()')
(我觉得'
字符还是比较危险的,不应该忽略)。 - script 标签的文本部分:
第一种情况应该不允许用户输入。<script> var value = {user_input}; var value = "{user_input}"; </script>
第二种情况要考虑 value 之后怎么使用(比如有没有用来 eval,有没有用来生成 HTML 等)。即使后续使用没有在任何有危险的地方,它的处理也很麻烦。OWASP 的建议是把所有 ASCII 字符,除字母和数字以外,都转成\xHH
的形式。
代码我就懒得写了,避免出现这种情况才是正道。有兴趣的可以看 ESAPI 的实现,有很多种语言的版本。 - style 标签的文本部分:
都不应该允许用户输入。某些允许用户自定义样式的网站,如果会对别人有效,应该提供模板和可选的值,因为这里面能 XSS 攻击的地方太多了。<style> {user_input} body {background-url: "{user_input}"} </style>
此外,还有一些比较省事的解决方案,例如使用久经考验的模板引擎。但建议在已经熟悉 XSS 解决方案的基础上再使用,否则很可能即使用了也还是有 XSS 漏洞。
很多 XSS 风险其实也是来自图省事的心态。如果创建 HTML 元素时,用
document.createElement
方法,然后手动设置各个属性和子元素,自然会安全很多;但拼成字符串,然后直接传给 jQuery()
或 innerHTML
无疑会方便很多,可是风险也就随之突显出来了。最后,本文还只是粗浅地介绍了一些常见的解决方案,而 XSS 的种类却远非这么几种。
所有输出了不可信数据的地方,都有被 XSS 攻击的可能性。多思考一下用户的输入是否能达到意想不到的结果,并且随着时间的推移,长期地去检视这些可能的隐患点,因为未来很可能出现之前没有发现的攻击方式(例如浏览器的 bug 和没有正确地配置 web 服务器等)。
向下滚动可载入更多评论,或者点这里禁止自动加载。