「在表单里输入的文字,却在另一个人的画面上作为脚本运行起来」——这就是 XSS。本文从零讲解其原理与可靠的防范方法(不会写出攻击步骤)。
三种类型
XSS 按「恶意字符串经由何处被执行」分为三种。
| 类型 | 如何被植入 |
|---|---|
| 存储型(Stored) | 被保存在帖子、个人资料等处,在所有浏览者的浏览器中执行(最危险) |
| 反射型(Reflected) | URL 参数等被原样反射到页面上,在点击了链接的人那里执行 |
| DOM 型(DOM-based) | 不经由服务器,由浏览器端的 JS 危险地处理输入而引发 |
为什么危险(原理)
浏览器会把页面中写的 <script> 当作「正规代码」执行。当攻击者的字符串原样作为 HTML 混入页面时,浏览器无法把它和真正的代码区分开。
危害是「在那个页面上能做的一切」。如果说 RCE 是服务器端的最坏情况,那么 XSS 就相当于客户端(用户)的最坏情况。
防御:真正关键的是「输出时转义」
XSS 的基本原则是用「输出」而非「输入」来防。在把值显示到画面上的那一刻,将其转换为不会被当作 HTML 解析的形式。
输出时转义(最重要)
把来自用户的值显示到画面上时,将 < > & " ' 等转换为 HTML 实体。要按输出所处的位置(HTML 正文·属性·JS·URL)采用各自正确的方式。
不要破坏框架的自动转义
React/Vue/各类模板默认会自动转义。只要避免 dangerouslySetInnerHTML 或 v-html 这类「直接插入生 HTML」的做法,就能防住大部分情况。
用 CSP(Content-Security-Policy)做多层防御
万一有遗漏,也能在浏览器端阻止未经许可的脚本执行。通过限制内联脚本等手段把危害降到最低。
保护 Cookie
给会话 Cookie 加上 HttpOnly,让 JS 无法读取。即便被窃取也能限制危害范围。
本站的观点:最大的防御是「别自己开洞」
现代框架默认就倾向于安全一侧。许多 XSS 正是在开发者 「想直接放入 HTML」而刻意关掉自动转义的那一刻产生的。如果确实要允许 HTML,就不要用自己写的字符串处理,而要经过久经验证的 sanitize 工具。「为了方便而放弃安全」才是最大的风险。
接下来阅读
FAQ
QXSS 会造成什么后果?
攻击者植入的脚本会作为真实页面的一部分在受害者的浏览器中执行。结果可能导致会话被窃取(冒充登录)、输入被偷看、页面被篡改、自动执行其他操作等。
QXSS 最主要的防御是什么?
是「输出时转义」。把来自用户的值显示到页面上时,将其转换为不会被当作 HTML 解析的形式。React/Vue 等模板默认就会这样做,所以不要自己破坏这套自动转义(避免使用 dangerouslySetInnerHTML 等)就是最有效的对策。此外再用 CSP 做多层防御。
Q在输入时做 sanitize 就够了吗?
往往不够。关键在于「按输出所处的上下文(HTML 正文·属性·JS·URL)分别正确地 escape」。只靠输入过滤,会因为搞错上下文而出现遗漏。只有在确实要允许 HTML 时,才使用可信赖的 sanitize 工具。