1. 项目概述:为什么DOM型XSS是前端安全的“幽灵”?
如果你是一名Web开发者,或者对安全稍有涉猎,一定听说过XSS(跨站脚本攻击)。但提到DOM型XSS,很多人的第一反应可能是:“不就是XSS吗?我用了CSP(内容安全策略)或者对输入做了转义,应该没问题了吧?” 这正是DOM型XSS最危险的地方——它像一个潜伏在前端代码里的“幽灵”,常规的服务器端防御手段对它常常无效。我见过太多项目,后端安全做得滴水不漏,却因为前端JavaScript代码里一个不经意的 innerHTML 或 eval 调用,导致整个防线被轻易绕过。
简单来说,DOM型XSS是一种完全发生在客户端浏览器中的攻击。攻击者的恶意脚本并非由服务器“发送”给浏览器,而是浏览器在解析和渲染页面时,通过JavaScript操作文档对象模型(DOM)的过程“自己生成”并执行了恶意代码。这个过程绕开了服务器,因此服务器端的日志里可能风平浪静,但用户的浏览器却已沦陷。随着现代Web应用越来越依赖JavaScript来构建动态、交互性强的单页面应用(SPA),DOM操作变得无比频繁,DOM型XSS的风险也随之急剧升高。理解它,不仅是安全工程师的必修课,更是每一位前端开发者的责任。
2. DOM型XSS的核心原理拆解:从源头到执行
要防御一个敌人,必须先了解它的攻击路径。DOM型XSS的原理可以清晰地分为三个阶段:污染源、传播途径和危险接收器。
2.1 污染源:恶意数据从何而来?
攻击者的恶意输入必须有一个入口。在DOM型XSS中,这个入口通常是那些可以被用户控制、且最终会被JavaScript处理的客户端数据源。最常见的有:
- URL片段(Fragment) :即URL中
#号后面的部分。例如,https://example.com/page#userInput。这部分内容不会发送到服务器,但客户端的JavaScript可以通过location.hash属性轻松读取。 - URL查询参数(Query String) :即
?后面的参数。虽然这部分会发送到服务器,但攻击者构造的恶意URL可以直接包含它。JavaScript通过location.search或URLSearchParamsAPI读取。 - Web存储 :包括
localStorage、sessionStorage和Cookie。如果应用从这些存储中读取数据并动态地写入DOM,而存储的数据先前被攻击者污染过(例如通过另一个漏洞),就会触发DOM型XSS。 - 客户端重定向 :通过
window.name或document.referrer等属性传递数据,这些属性也可能被跨页面操控。 - 来自服务器的“安全”数据 :听起来矛盾,但确实存在。服务器可能返回了一段看似安全、已经过转义的数据,但前端JavaScript错误地对其进行了解码(例如,先
decodeURIComponent,再写入DOM),或者进行了二次拼接,从而引入了风险。
注意 :这里有一个关键认知偏差需要纠正。开发者常常认为“来自我服务器数据库的数据是安全的”。但在DOM型XSS的语境下,安全性不取决于数据来源是否“可信”,而取决于数据最终是否被“当作代码执行”。一段从自己数据库读出、但未经妥善处理就交给危险DOM API的“干净”数据,如果其中包含了用户先前注入的恶意脚本,同样危险。
2.2 传播途径:JavaScript如何搬运“炸弹”?
污染源的数据被JavaScript代码读取后,并不会立刻爆炸。它需要在应用程序的逻辑中被传递、拼接、处理。这个传播过程就像搬运一颗炸弹,代码的逻辑决定了炸弹被运往何处。典型的传播模式包括:
- 直接传递 :
var userData = location.hash.substring(1); document.getElementById('output').innerHTML = userData;这里,从location.hash读取的数据直接流向了innerHTML。 - 字符串拼接 :这是更隐蔽也更常见的情况。
var message = 'Welcome, ' + userName + '!'; document.write(message);如果userName来自污染源,整个拼接后的字符串就变得危险。 - 经过复杂处理 :数据可能经过一系列函数调用、条件判断、循环处理,最终流向输出点。追踪这种数据流需要一定的代码分析能力。
2.3 危险接收器:引爆点在哪里?
数据流的终点,即那些能够将字符串解析为HTML或JavaScript代码并执行的DOM API或属性,就是“危险接收器”。这是漏洞被触发的最后一步。主要分为几类:
- HTML注入接收器 :将字符串当作HTML解析。
-
element.innerHTML -
element.outerHTML -
document.write()/document.writeln() - 某些特殊场景下的
iframe.srcdoc
-
- JavaScript代码注入接收器 :将字符串当作JavaScript代码执行。
-
eval()函数 -
setTimeout()/setInterval()的第一个参数是字符串时(如setTimeout("alert("+data+")", 1000)) -
Function()构造函数 -
location.href = 'javascript:...'(通过javascript:协议)
-
- 属性注入接收器 :将字符串设置为某个元素的属性,如果属性值未经处理,可能跳出属性上下文,闭合引号,注入新的事件或脚本。
-
element.setAttribute('onclick', data)(事件处理器属性) - 动态修改
<script>标签的src属性为可控数据。
-
理解这三个环节,就掌握了DOM型XSS的攻击链。防御的本质,就是在数据从“污染源”流向“危险接收器”的“传播途径”上,设置有效的


397

被折叠的 条评论
为什么被折叠?



