手把手教你防御 XSS 攻击

攻击分类

XSS 攻击主要可以分为三类

  1. 存储型(持久型)(server端缺陷)
  2. 反射型(server 端缺陷)
  3. DOM 型(浏览器端缺陷)

接下来将从上面的攻击类型分别给出防御方案

反射型

为什么叫 ta 反射型,大概是因为

由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。

而像上面这个是一个搜索的例子,用户需要点击下面这个链接

http://localhost:3000/search?search=<script>alert("反射型 XSS 攻击")</script>

这个反射型 XSS 攻击之所以能够成功的原因是后端未对用户提交的内容做校验,具体逻辑如下

/**
 * 基于用户搜索内容返回 /search 页面内容
 * @param {string} search 
 */
exports.generateSearchHTML = (search = "") => {
  return `
    <body>
      <form
        action="http://localhost:3000/search"
        method="GET"
        enctype="application/json"
      >
        搜索:<input class="search-input" name="search" type="text" />
        <br />
        搜索内容:${search}
        <br />
        <button class="confirm-button" type="submit">确认</button>
      </form>
    </body>
  `;
};
复制代码

此类后端渲染直接拼接返回 HTML 字符串是主要原因,所以防御的主要手段就是对可能的恶意代码片段进行转义

啥是转义呢?为什么不直接把可能是恶意代码删掉就行了呢?

比如 <script>alert("反射型 XSS 攻击")</script> 里的 < > / 这几个字符,我删掉就行了

这样操作其实不太严谨,毕竟不是所有和 < > / 相关的内容都是恶意的,就比如我数学不好,我去搜索 5 < 7 的答案是多少,很合理吧?

所以过滤和删除是不行的,所以就要采用转义

转义(Escape)

有一些特别的字符被保留用于 HTML 中,这意味着浏览器会将这些字符解析为 HTML 代码。例如,如果你使用小于号(<),浏览器会将其后的文本解析为一个 tag

HTML 有一些特殊字符,就比如 < > / 这种字符,如果你想要在浏览器展示,想要 HTML 能够正常渲染,可以采用浏览器提供的实体(Entity

比如说

<span>123&amp;</span>
复制代码

实际效果就是 123&

这个转义的意思和正则表达式的转义应该是差不多的

下面这个是文章使用到的转义字符的对照表

字符十进制转义字符
"&#34;&quot;
&&#38;&amp;
<&#60;&lt;
&#62;&gt;
不断开空格(non-breaking space)&#160;&nbsp;

更详细的对照表请参考下面的两个表

  1. HTML转义字符常用对照表 - OSCHINA
  2. 字符实体的官方列表 - 这个格式乱乱的,建议看上面这个

防御实操

回到反射型 XSS 攻击的防御,可以在 Server 端采用转义的方式解决,代码如下

// ...
exports.generateSearchHTML = (search = "") => {
  /**
   * 转义字符串
   * @param {string} originStr
   * @returns
   */
  const escape = (originStr) => {
    let str = originStr;
    str = str.replace(/</g, "&lt;");
    str = str.replace(/>/g, "&gt;");
    str = str.replace(/"/g, "&quot;");
    str = str.replace(/'/g, "&#x27;");
    str = str.replace(/\//g, "&#x2F;");
    return str;
  };

  return `
    ...
        搜索内容:${search}
    ...
  `;
};
复制代码

我的正则应该还是非常 66666

效果如下

直接将攻击者的恶意代码渲染,并且不会执行,看看这次生成的 HTML

<body>
  <form
    action="http://localhost:3000/v2/search"
    method="GET"
    enctype="application/json"
  >
    搜索:<input class="search-input" name="search" type="text" />
    <br />
    搜索内容:&lt;script&gt;alert(&quot;反射型 XSS
    攻击&quot;)&lt;&#x2F;script&gt;
    <br />
    <button class="confirm-button" type="submit">确认</button>
  </form>
</body>
复制代码

特殊字符都被转义了,恶意代码不会被执行

存储型和 DOM 型

如果你看过这篇我的前篇文章就知道,其实所谓的 XSS 攻击的三种分类都是一样的,对前端来说就是,获取变量然后渲染,XSS 在其中就是

  1. 提交恶意代码
  2. 浏览器执行恶意代码

XSS 攻击的处理和场景辨别是一个需要经验的知识,文章不可能介绍的了全部,尽可能的防御是对需要转义的场景可以采用受业界广泛采用的库,比如 leizongmin/js-xss

npm install xss
复制代码

这样库出问题的时候就大家都出问题了,法不责众,哈哈哈哈哈哈哈哈哈

但是对于 XSS 攻击的防御还有最后一个需要介绍的

转义时机

XSS 攻击步骤

  1. 提交恶意代码
  2. 浏览器执行恶意代码

前端

首先前端转义再提交后端不靠谱,攻击者只要模拟发起请求,绕过前端,后端请求处理比较靠谱

后端

对于反射型来说,不需要存入数据库,而且需要拼接字符串,但对于存储型 XSS 攻击的入库内容就不太一样,比如下面这个场景

用户提交了一个评论内容,但是如果这个获取评论的 GET 请求 iOS/Andorid/Web 都需要展示呢?

比如内容在 5 < 7

如果在存入数据库时转义,那么数据库实际存储内容就是 5 &lt; 7

那么 iOS/Andorid 端请求时并展示时就是 5 &lt; 7

而对于 Web 来说,如果有用到这个请求的某个页面有 SEO 需求做了 SSR 处理,则拼接 HTML 字符串可以正常展示,而对于有用到这个请求的某个页面是使用 Ajax 来展示,则也会显示 5 &lt; 7

具体如下图

实际操作中可以

  1. 恶意代码存储进库,保证浏览器不执行就行了,拼接 HTML 处理即可
  2. 先过滤存储,在实际多端 GET 请求时再反转义一次,比如

iOS/Android 由 5 &lt; 7 转义至 5 < 7

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值