深入解析XSS攻击:从原理到防御的全方位指南

XSS

一、什么是XSS?

  1. Cross-Site Scripting(跨站脚本攻击)为了与CSS作区分故简称 XSS,是一种代码注入攻击 。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全。
  2. 本质
    • 恶意代码未经过滤,与网站正常的代码混在一起;浏览器无法分辨哪些脚本是可信的,导致恶意脚本被执行。
  3. 可能被注入恶意脚本的内容
    • 来自用户的 UGC 信息
    • 来自第三方的链接
    • URL 参数
    • POST 参数
    • Referer (可能来自不可信的来源)
    • Cookie (可能来自其他子域注入)

二、XSS攻击方式

  1. 分为三种:存储型 、反射型 、DOM 型

存储型XSS
  • 不需要用户手动触发,所有浏览者访问页面时都会被XSS
  • 常见于带有用户保存数据 的网站功能,如论坛发帖、商品评论、用户私信等
反射型XSS
  • 需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击
  • 常见于通过 URL 传递参数 的功能,如网站搜索、跳转等
DOM型XSS
  • 属于前端 JavaScript 自身的安全漏洞
区别
特征 存储型 XSS 反射型 XSS DOM 型 XSS
存储位置 恶意代码存储在服务器数据库 恶意代码不存储,仅存在于URL参数 恶意代码不存储,存在于URL片段
触发条件 用户访问被感染的页面 用户点击包含恶意参数的链接 用户点击包含恶意片段(#)的链接
数据流 服务器 → 用户浏览器 服务器反射参数 → 用户浏览器 完全在客户端处理(不经过服务器)
持久性 长期存在(直到数据被删除) 一次性(仅当用户点击链接时) 一次性(仅当用户点击链接时)
攻击范围 所有访问该页面的用户 仅点击特定链接的用户 仅点击特定链接的用户

三、常见的XSS攻击场景

社交媒体评论------存储型XSS
  • 攻击者在评论区提交恶意脚本(如 <script>sendCookiesToAttacker()</script>),该评论被存储到服务器 。其他用户访问该页面时,恶意脚本自动加载并窃取其会话Cookie。
html 复制代码
<!-- 用户评论内容 -->
<div class="comment">
  这篇文章写得真好!<script>sendCookiesToAttacker()</script>
</div>
搜索功能------反射型XSS
  • 网站搜索功能未过滤输入参数,攻击者构造恶意链接:https://example.com/search?q=<script>alert('XSS攻击')</script>,用户点击链接 后,服务器返回的页面包含未过滤的恶意脚本。
单页面应用路由------DOM型XSS
  • SPA前端路由根据URL参数动态加载内容,但未对参数过滤。攻击者构造链接:https://example.com/#/profile?username=<script>alert('XSS攻击')</script>,用户点击链接前端脚本username 参数直接渲染到页面,触发XSS。

四、防御策略

预防存储型XSS

复制代码
 ##### 输入过滤

 * 对于**明确的输入类型**

   * 如数字、URL、电话号码、邮件,使用`escapeHTML()`可以把用户的输入内容进行转义

   | 原始字符 | 转义后实体编码 |
   |:-----|:--------|
   | `<`  | `&lt`   |
   | `>`  | `&gt`   |
   | `&`  | `&amp`  |
   | `"`  | `&quot` |
   | `'`  | `&#x27` |

 ```js
 // 用户输入:<script>alert('XSS攻击')</script>
 const escaped = escapeHTML(userInput);
 document.getElementById('content').innerHTML = escaped;
 // 输出:&lt;script&gt;alert('XSS攻击')&lt;/script&gt;
 ```

 * 更推荐使用成熟且完善的**现有库**

 ```js
 // 使用 DOMPurify(支持更复杂的净化)
 import DOMPurify from 'dompurify';
 DOMPurify.sanitize(userInput, { ALLOWED_TAGS: [] }); 
 ```

 * **特殊场景**补充

 | 场景     | 额外需要转义的字符         | 方法                        |
 |:-------|:------------------|:--------------------------|
 | URL参数  | `%` `#`           | 配合使用 `encodeURIComponent` |
 | CSS属性  | `\` `()`          | 使用 `CSS.escape()`         |
 | JSON数据 | `\u2028` `\u2029` | `JSON.stringify`          |
  • 对于不明确的类型

    • 最好不要使用输入过滤,因为当把转义后的数据发送到后端再回到前端,赋值给一个变量 之后,展示出来的效果会变成乱码 (如5<7会变成5&lt7

预防存储型XSS和反射型XSS

复制代码
 ##### 纯前端渲染

 * 现代框架的自动防护

 * **React**:默认转义所有插值内容

   ```react
   function UserContent({ text }) {
     return <div>{text}</div>;  // 自动转义 `<` `>` 等字符
   }
   // 用户输入 `<script>alert('XSS攻击')</script>` 会显示为文本,不会执行
   ```

 * **Vue**:模板插值自动编码

   ```vue
   <template>
     <div>{{ userInput }}</div>  <!-- 自动转换为文本 -->
   </template>
   ```

 * **Angular**:插值绑定安全处理

   ```html
   <div>{{ userInput }}</div>  <!-- 输出内容自动转义 -->
   ```

 | 安全API                                    | 危险替代品                | 场景      |
 |:-----------------------------------------|:---------------------|:--------|
 | `textContent`                            | `innerHTML`          | 插入纯文本内容 |
 | `setAttribute`                           | `innerHTML`          | 设置元素属性  |
 | `document.createElement` + `appendChild` | `insertAdjacentHTML` | 动态创建节点  |
复制代码
 ##### 转义HTML

 * 使用更完善更细致的转义库`org.owasp.encoder`,以下代码引用自 [org.owasp.encoder 的官方说明](https://link.juejin.cn/?target=https%3A%2F%2Fwww.owasp.org%2Findex.php%2FOWASP_Java_Encoder_Project%23tab%3DUse_the_Java_Encoder_Project "https://link.juejin.cn/?target=https%3A%2F%2Fwww.owasp.org%2Findex.php%2FOWASP_Java_Encoder_Project%23tab%3DUse_the_Java_Encoder_Project")。

   ```html
   <!-- HTML 标签内文字内容 -->
   <div><%= Encode.forHtml(UNTRUSTED) %></div>

   <!-- HTML 标签属性值 -->
   <input value="<%= Encode.forHtml(UNTRUSTED) %>" />

   <!-- CSS 属性值 -->
   <div style="width:<= Encode.forCssString(UNTRUSTED) %>">

   <!-- CSS URL -->
   <div style="background:<= Encode.forCssUrl(UNTRUSTED) %>">

   <!-- JavaScript 内联代码块 -->
   <script>
     var msg = "<%= Encode.forJavaScript(UNTRUSTED) %>";
     alert(msg);
   </script>

   <!-- JavaScript 内联代码块内嵌 JSON -->
   <script>
   var __INITIAL_STATE__ = JSON.parse('<%= Encoder.forJavaScript(data.to_json) %>');
   </script>

   <!-- HTML 标签内联监听器 -->
   <button
     onclick="alert('<%= Encode.forJavaScript(UNTRUSTED) %>');">
     click me
   </button>

   <!-- URL 参数 -->
   <a href="/search?value=<%= Encode.forUriComponent(UNTRUSTED) %>&order=1#top">

   <!-- URL 路径 -->
   <a href="/page/<%= Encode.forUriComponent(UNTRUSTED) %>">

   <!--
     URL.
     注意:要根据项目情况进行过滤,禁止掉 "javascript:" 链接、非法 scheme 等
   -->
   <a href='<%=
     urlValidator.isValid(UNTRUSTED) ?
       Encode.forHtml(UNTRUSTED) :
       "/404"
   %>'>
     link
   </a>

   ```

预防DOM型XSS

  • 使用.innerHTML.outerHTMLdocument.write() 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent.setAttribute()
  • DOM 中的内联事件监听器,如 locationonclickonerroronloadonmouseover 等,<a> 标签的 href 属性,JavaScript 的 eval()setTimeout()setInterval() 等,都能把字符串作为代码运行

其他预防方法

  • httpOnly: 在 cookie 中设置 HttpOnly 属性后,js脚本将无法读取到 cookie 信息。

    js 复制代码
    // 后端实现
    app.post('/login', (req, res) => {
      res.cookie('sessionID', 'xxxx', {
        httpOnly: true,  // 关键:禁止JS访问
        secure: true,    // 仅通过HTTPS传输
        sameSite: 'Lax'  // 防御CSRF
      });
      res.send('登录成功');
    });
  • 白名单:

    • 输入白名单验证

      js 复制代码
      function validateInput(input) {
          // 只允许字母、数字、空格以及特定的标签
          const whiteListPattern = /^[a-zA-Z0-9\s<>/bB/iI]*$/;
          return whiteListPattern.test(input);
      }
      const userInput = "<script>alert('xss')</script>";
      if (!validateInput(userInput)) {
          // 处理非法输入
          console.log("Invalid input!");
      }
    • HTML标签/属性白名单

      js 复制代码
      // 使用DOMPurify库配置白名单
      import DOMPurify from 'dompurify';
      
      const dirtyHTML = '<b class="safe">合法内容</b><script>恶意代码</script>';
      const cleanHTML = DOMPurify.sanitize(dirtyHTML, {
        ALLOWED_TAGS: ['b', 'i', 'em'],     // 允许的标签
        ALLOWED_ATTR: ['class', 'style'],   // 允许的属性
        FORBID_TAGS: ['style', 'script']    // 强制禁止的标签
      });
      
      console.log(cleanHTML); 
      // 输出:<b class="safe">合法内容</b>
  • CSP:

    • 基础配置

      http 复制代码
      Content-Security-Policy:
        default-src 'self';     # 默认只允许同源资源
        script-src 'self'       # 脚本仅限同源
          https://trusted.cdn.com 
          'nonce-abc123';       # 允许带特定nonce的内联脚本
        style-src 'self' 'unsafe-inline'; # 允许内联样式(慎用)
        img-src * data:;        # 允许所有图片源(根据需求收紧)
        font-src 'self'; 
        frame-src 'none';       # 禁止嵌入iframe
        report-uri /csp-report; # 违规报告地址

参考资料 juejin.cn/post/684490... juejin.cn/post/684490...

相关推荐
Momo__27 分钟前
VueUse createReusableTemplate —— 单文件组件内的模板复用神器
前端·vue.js
程序员小富34 分钟前
我开源了一个开发者专属的智能 JSON 工具,得到了媳妇高度认可
前端·vue.js·后端
小小小小宇34 分钟前
程序员如何给 LLM 装工具以及看懂推理过程
前端
写代码的皮筏艇34 分钟前
React中的forwardRef
前端·react.js·面试
槑有老呆43 分钟前
花三个月工资请了个 AI 程序员,结果它连青岛啤酒股价都查不了
前端
风骏时光牛马1 小时前
Verilog开发常见问题汇总解析
前端
子兮曰1 小时前
AI Coding Method Map:一张图看懂 AI 编程的完整链路
前端·人工智能·后端
weedsfly1 小时前
语法糖褪去之后——Babel 转译产物中的 JavaScript 本貌
前端·javascript
JustHappy1 小时前
「软件设计思想杂谈🤔」“切图仔”也能懂编译原理?框架源码也许没那么难。聊聊 Vue 的编译(上)
前端·javascript·vue.js