手把手教你防御 XSS 攻击
继上一篇文章介绍了如何实现 XSS
攻击之后,后面留下了伏笔,我们应该如何去防御文章介绍的 XSS
各种攻击呢?废话少说,直接上车!
您可以在线查看完整的示例源代码
攻击分类
XSS
攻击主要可以分为三类
- 存储型(持久型)(
server
端缺陷) - 反射型(
server
端缺陷) 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&</span>
实际效果就是 123&
这个转义的意思和正则表达式的转义应该是差不多的
下面这个是文章使用到的转义字符的对照表
字符 | 十进制 | 转义字符 |
---|---|---|
" | " | " |
& | & | & |
< | < | < |
> | > | |
不断开空格(non-breaking space) |   | |
更详细的对照表请参考下面的两个表
- HTML转义字符常用对照表 - OSCHINA
- 字符实体的官方列表 - 这个格式乱乱的,建议看上面这个
防御实操
回到反射型 XSS
攻击的防御,可以在 Server
端采用转义的方式解决,代码如下
// ...
exports.generateSearchHTML = (search = "") => {
/**
* 转义字符串
* @param {string} originStr
* @returns
*/
const escape = (originStr) => {
let str = originStr;
str = str.replace(/</g, "<");
str = str.replace(/>/g, ">");
str = str.replace(/"/g, """);
str = str.replace(/'/g, "'");
str = str.replace(/\//g, "/");
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 />
搜索内容:<script>alert("反射型 XSS
攻击")</script>
<br />
<button class="confirm-button" type="submit">确认</button>
</form>
</body>
特殊字符都被转义了,恶意代码不会被执行
存储型和 DOM 型
如果你看过这篇我的前篇文章就知道,其实所谓的 XSS
攻击的三种分类都是一样的,对前端来说就是,获取变量然后渲染,XSS
在其中就是
- 提交恶意代码
- 浏览器执行恶意代码
XSS
攻击的处理和场景辨别是一个需要经验的知识,文章不可能介绍的了全部,尽可能的防御是对需要转义的场景可以采用受业界广泛采用的库,比如 leizongmin/js-xss
npm install xss
这样库出问题的时候就大家都出问题了,法不责众,哈哈哈哈哈哈哈哈哈
但是对于 XSS
攻击的防御还有最后一个需要介绍的
转义时机
XSS
攻击步骤
- 提交恶意代码
- 浏览器执行恶意代码
前端
首先前端转义再提交后端不靠谱,攻击者只要模拟发起请求,绕过前端,后端请求处理比较靠谱
后端
对于反射型来说,不需要存入数据库,而且需要拼接字符串,但对于存储型 XSS
攻击的入库内容就不太一样,比如下面这个场景
用户提交了一个评论内容,但是如果这个获取评论的 GET
请求 iOS/Andorid/Web
都需要展示呢?
比如内容在 5 < 7
如果在存入数据库时转义,那么数据库实际存储内容就是 5 < 7
那么 iOS/Andorid
端请求时并展示时就是 5 < 7
而对于 Web
来说,如果有用到这个请求的某个页面有 SEO
需求做了 SSR
处理,则拼接 HTML
字符串可以正常展示,而对于有用到这个请求的某个页面是使用 Ajax
来展示,则也会显示 5 < 7
具体如下图
实际操作中可以
- 恶意代码存储进库,保证浏览器不执行就行了,拼接
HTML
处理即可 - 先过滤存储,在实际多端
GET
请求时再反转义一次,比如
iOS/Android
由 5 < 7
转义至 5 < 7
参考资料
转载自:https://juejin.cn/post/7177015789382271035