• XSS高级 svg 复现一个循环问题以及两个循环问题


    例子1:

    源码:

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Documenttitle>
    head>
    <body>
        
    body>
    <script>
        const data = decodeURIComponent(location.hash.substring(1));
        const root = document.createElement('div');
        root.innerHTML = data;
    
        //这里模拟了xss过滤的过程,方法是移除所有属性
        for(let el of root.querySelectorAll('*')) {
            for (let attr of el.attributes) {
                el.removeAttribute(attr.name);
            }
        }
    
        document.body.appendChild(root);
    script>
    html>
    
    • 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

    因为现在是没有任何属性的,所以输出aaa

    在这里插入图片描述

    输入这样的标签确实没有任何属性,但是源码中是innerHTML,所以只能用img标签
    在这里插入图片描述

    输入,发现把src属性删掉了
    在这里插入图片描述

    我们去断点调试一下,看看究竟为什么只删除了src属性,没有删除onerror属性

    1、首先el是没有值的

    在这里插入图片描述

    2、el有值了,它是img,因为img传进来了
    在这里插入图片描述

    关键的是去看img中的attributes,有两个属性,一个src,一个onerror,看上去像数组一样的一个东西,有索引0和索引1。

    在这里插入图片描述

    3、继续走,attr把第一个选中了,选中了src属性

    在这里插入图片描述

    下面是remove,把src属性移除掉,onerror属性向前移一位,然后索引因为没有下一位了,所以循环结束,onerror属性也就留了下来

    基于这个点,我们怎么去绕过呢?调整顺序,被过滤掉13属性,留下了24属性

    输入
    在这里插入图片描述

    在这里插入图片描述

    成功绕过!

    这种做法还有很多:

    这是循环删除的方式,那现在改变它,不用边循环边删除

    例子2:

    源码:(这次先把属性放在数组中,然后再数组中循环删除)

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Documenttitle>
    head>
    <body>
        
    body>
    <script>
        const data = decodeURIComponent(location.hash.substring(1));
        const root = document.createElement('div');
        root.innerHTML = data;
    
        //这里模拟了xss过滤的过程,方法是移除所有属性
        for(let el of root.querySelectorAll('*')) {
            let attrs = [];
            for (let attr of el.attributes) {
                attrs.push(attr.name);
            }
            for (let name of attrs) {
                le.removeAttribute(name);
            }
        }
    
        document.body.appendChild(root);
    script>
    html>
    
    • 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

    这次再测试发现无效了

    在这里插入图片描述

    img标签的属性也被删除了

    在这里插入图片描述

    接下来有两种方式来解决

    方法一:要么别进循环直接执行

    方法二:要么进循环删除无用元素

    dom cobbing

    一个svg一个DOM

    方法二:进循环删除无用数据

    但svg刚刚试了不行,把onload属性删掉了

    如果有一个元素可以劫持el.attributes,那也许删除的就不是el,而是el的一个子元素

    <body>
        <form id="">
            <img name="attributes">img>
        form>
    body>
    <script>
        console.log(window.x.attributes)
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    我们插入一个form,那el此时就是form,el.attributes是谁?是form里的img

    那我们让img进循环把img属性删掉,然后我们的form触发弹窗就OK了

    输入

    在这里插入图片描述

    事实上img已经进来了,但是不是可迭代对象,所以for循环无法进行,一个元素肯定不是可迭代对象,因此我们需要把它组成一个集合或者数组。

    两个标签即可搞定

    一直锁定聚焦:

    <form tabindex=1 onfocus="alert(1)" autofocus="true"><input name=attributes><input name=attributes>form>
    
    • 1

    改进:聚焦生效一次后删除:

    form>
    • 1

    成功绕过!

    通过debug分析:form确确实实走进来了,但是走进来归走进来,它删除的是后面的两个input,和我前面的触发语句没有什么关系,为什么?因为你仅仅是删除了input的属性,并没有把input的标签删除,这样我的tabindex还是聚焦在这里。

    虽然你升级了,但是我们依然通过dom破坏的方式绕过了

    方法一:别进循环

    输入

    在这里插入图片描述

    两个SVG成功了,分析---->为什么?

    分析

    我们把过滤注释掉,去断点分析:

    在这里插入图片描述

    JS走了一步走到底部了

    在这里插入图片描述

    再走一步,走到img了,显示打印alert(1)但还没弹窗,很明显js把dom树阻塞了,js执行完毕,img才开始执行,意味着img标签逃不过js的过滤。

    alert(1)是在页面上script标签中的代码全部执行完毕后才被调用的。

    测试代码谁前谁后

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Documenttitle>
    head>
    <body>
        
    body>
    <script>
        window.addEventListener("DOMContentLoaded", (event) => {
            console.log('DOMContentLoaded')
        });
        window.addEventListener("load", (event) => {
            console.log('load')
        });
        const data = decodeURIComponent(location.hash.substring(1));
        const root = document.createElement('div');
        root.innerHTML = data;
    script>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    可见当我们的JS执行完毕之后DOMContentLoaded(DOM的内容被加载进来),加载进来以后才执行img的属性

    JS先执行,img后执行

    在这里插入图片描述

    浏览器渲染部分内容:

    1、js先执行,js执行完毕,DOM树构建完成

    2、触发DOMContentLoaded事件

    3、加载脚本、图片(img,css)等外部标签

    4、触发load事件

    结论:js可以阻塞DOM树的构建,导致js先执行,img后执行

    研究两个svg为何可以绕过

    两个svg degbug调试看执行的先后顺序
    第一步

    在这里插入图片描述

    第二步

    在这里插入图片描述

    第三步
    在这里插入图片描述

    底层原理

    原理:如果是一个svg,则直接执行到这里就return返回了,因为它是最外层

    返回之后继续往下走,在script标签执行完毕,再执行第二个
    在这里插入图片描述

    而一旦最外层svg加载了一个子元素svg,则直接加载了一个onload事件执行了

    最开始判断的是最外层的svg,最外层的是return,如果不是最外层则走的是下面那个方法sendsvGLoadEventIfPossible();

    在这里插入图片描述

    判断:如果你是svg元素,并且你有一个相应的onload事件,则创建一个load事件(触发代码直接执行)

    在这里插入图片描述

    只要你前面创建了svg元素,并且对load事件编写了相关的代码,满足这俩个条件你就可以进入这个if中,就可以直接加载你的onload事件

    如果你是最外层的话直接退出往下走,如果你不是最外层,它在innerHTML那里直接加载onload事件直接在那执行!

    测试代码:

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Documenttitle>
    head>
    <body>
        
    body>
    <script>
        window.addEventListener("DOMContentLoaded", (event) => {
            console.log('DOMContentLoaded')
        });
        window.addEventListener("load", (event) => {
            console.log('load')
        });
        const data = decodeURIComponent(location.hash.substring(1));
        const root = document.createElement('div');
        root.innerHTML = data;
    script>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    URL输入的值:

    
    
    • 1

    console回馈如下

    在这里插入图片描述

    可以很明显的看到,最外层的是svg0,除了外层,里面的是svg2和svg1

    很明显的看到在DOM没加载之前svg2和svg1就执行了,而最外层的svg0执行在DOM加载完之后。

    那么就很好解释为什么俩个svg触发了,而一个svg没有触发了。

    内层的svg在DOM构建之前就执行了,而最外层的svg在DOM树构建完成之后才执行。

  • 相关阅读:
    CentOS7二进制安装和YUM安装mongodb,服务器无法安装5.0以上的 mongodb 数据库报错 Illegal instruction
    Denodo通过重要任命新增执行团队成员:Daniel Lender担任首席财务官,Stephen Welles担任首席法务官
    【Linux】awk命令简单用法
    机器学习实验六:决策树-海洋生物例子
    ajax实现文件的下载
    [word] 如何在word中插入地图? #学习方法#其他
    UE4和C++ 开发-C++与UMG的交互2(C++获取UMG的属性)
    undefined reference to symbol ‘g_signal_connect_data‘
    1055 集体照(测试点3, 4, 5)
    基础算法(六):回溯算法
  • 原文地址:https://blog.csdn.net/weixin_44811851/article/details/126197135