挑战概述
挑战地址位于:https://www.turb0.one/pages/Challenge_Two:_Stranger_XSS.html。 目标页面是一个可嵌入(frameable)的页面:https://www.turb0.one/files/9187cc52-fd4d-49c6-a336-0ce8b5139394/xsschal2minimal/inner.html。 该页面加载了三个脚本:
<script src="lodash.min.js"><script src="jquery-3.6.0.min.js"><script src="inner.js">
其中,前两个是常见的库(lodash和jQuery),第三个是自定义脚本。inner.js 的核心内容如下:
|
|
页面还通过一个 <meta> 标签设置了内容安全策略(CSP):
|
|
我们可以注意到以下几点关键信息:
- 使用 lodash 的
_.get:用于通过字符串路径(如"a.b.c")获取对象的嵌套属性值。 - jQuery 未被使用:这暗示jQuery可能是预期的脚本小工具(gadget)来源。作者选择尝试不依赖jQuery的解法。
- 隐式函数调用:在
fetch请求的body: hydrated.reqBody处,如果reqBody是一个对象,JavaScript会隐式调用其toString()方法。这是一个潜在的利用点。 - 可控的属性赋值:我们控制了
base.reqBody[to] = val;这个赋值操作。 - 对
postMessage事件使用_.get:这允许我们通过event.source和event.target等路径访问window对象。
仅通过赋值触发XSS
假设我们只想利用 base.reqBody[to] = val 这个赋值来触发XSS,我们的选择非常有限。常见的方式包括:
- 设置元素的HTML:
elm.innerHTML,elm.outerHTML,elm.insertAdjacentHTML - 设置元素的事件处理器属性:
elm.onclick等 - 导航至
javascript:链接:window.location,iframe.src - 设置iframe的
srcdoc属性:iframe.srcdoc
但CSP阻止了内联脚本的执行和 javascript: 导航。因此,我们需要利用同源框架(frame)来绕过CSP。
CSP绕过的关键
对于CSP,有两个关键点:
- 操作的发起位置:对于导航操作(如设置
location或iframe.src),浏览器会检查发起导航的文档的CSP。即使我们试图导航一个没有CSP的框架,只要发起请求的页面有CSP,也会被阻止。 - 触发对象的所在位置:对于DOM渲染操作(如设置
innerHTML或iframe.srcdoc),浏览器会检查目标框架的CSP。这意味着我们可以在一个有CSP的页面中,操作另一个同源但无CSP的页面的DOM,从而绕过CSP限制。
因此,攻击页面的基本结构是包含两个iframe:
- 框架A:指向挑战页面(有CSP)。
- 框架B:指向一个同源但不存在的页面(如404页面),该页面没有CSP。
这样,从框架A(挑战页面)我们可以执行 parent.frames[1].document.body.innerHTML = "<img src=x onerror=alert(document.domain)>",将XSS载荷注入到框架B中。
利用递归对象属性突破限制
然而,上述方法面临一个直接问题:base.reqBody[to] = val 的赋值无法直接设置 innerHTML 属性。我们发送的载荷通常如下:
|
|
这会导致 base.reqBody.x 包含对body元素的引用,但我们无法通过 base.reqBody.x 来修改其 innerHTML。如果 to 是 "innerHTML",我们又无法直接获取body元素本身。
作者尝试了原型污染(如设置 to: "__proto__"),但最终因 Illegal invocation 错误而失败。
关键的突破点在于:JavaScript允许对象递归引用。
我们可以构造一个递归对象,让 base.reqBody 指向其自身的一部分,然后在赋值过程中动态地将这个引用覆盖为我们真正想要操作的目标对象(如DOM元素)。
最终解决方案
完整的攻击者页面代码如下:
|
|
攻击流程如下:
- 第一个映射:
base.reqBody["reqBody"] = source.frames[1].document.body;。此时base.reqBody最初指向payload.base(即一个空对象{})。执行此赋值后,base.reqBody不再指向自身,而是被覆盖为对无CSP框架的document.body元素的引用。 - 第二个映射:
base.reqBody["innerHTML"] = data.xss;。此时base.reqBody已经是body元素,因此该操作等价于parent.frames[1].document.body.innerHTML = "<img src=x onerror=alert(document.domain)>"。 - XSS触发:载荷在无CSP的框架B中执行,成功弹出警告框。
总结与变体
核心要点:
- 利用对象递归引用,使属性赋值操作能够动态覆盖赋值目标本身。
- 利用同源框架的CSP差异,将XSS载荷注入到无CSP的上下文中执行。
基于此思路,还存在其他解法变体。例如,利用另一个框架的 srcdoc 属性:
|
|
甚至在挑战发布者Turb0修改后,作者又展示了另一个巧妙的利用方式,通过污染 Array.isArray 等方法来实现利用。