XSS
攻击理论知识学习完成的同学可以参考这篇一些攻击实现,理论背的再好还是要实践呀
您可以在线查看完整的示例源代码
攻击分类
XSS
攻击主要可以分为三类
- 存储型(持久型)(
server
端缺陷) - 反射型(
server
端缺陷) DOM
型(浏览器端缺陷)
接下来将从上面的攻击类型分别给出例子
反射型
通常一般教程先讲存储型,但是本篇文章基于实验先讲反射型,因为它和存储型有异曲同工之处,而且更底层
反射型通常出现于以下场景:常见于通过 URL
传递参数的功能,如网站搜索、跳转等
接下来就给出一个网站搜索的例子
下面是一个很常见的 search
页面例子
<!-- search.html -->
<body>
<form
action="http://localhost:3000/search"
method="GET"
enctype="application/json"
>
搜索:<input class="search-input" name="search" type="text" />
<br />
搜索内容:
<br />
<button class="confirm-button" type="submit">确认</button>
</form>
</body>
效果如下
注意 搜索内容: 这部分的 DOM
内容以及 URL
的参数 search
,这两个点其实就是 XSS
攻击的突破口
突破口
上面这个网站是一个由后端渲染的网站,那么它是怎么渲染的呢?
// user.js
router.get("/search", function (req, res, next) {
const { search } = req.query;
res.setHeader("Content-Type", "text/html");
res.send(generateSearchHTML(search));
});
exports.generateSearchHTML = (search = "") => {
return `
.....
搜索内容:${search}
.....
`;
};
其中 generateSearchHTML
将基于上面给出的 HTML
模板生成 HTML
文件内容,并返回给用户,并且会将用户输入的搜索内容,即 search
填充的 HTML
模板中,即 搜索内容:${search}
回到 XSS
攻击的本质就是执行恶意代码,换言之就是 <script></script>
的执行,回到上面的模板,如果转换成下面的结构,请问 <script></script>
是否会执行?
<body>
搜索内容:<script></script>
</body>
<script></script>
不要拘束与传统的三段式写法而以为 <body>
中的脚本不会执行,上面的代码能够顺利执行,因此执行 XSS
攻击的关键就是编写代码到搜索框中,后台返回给用户时即完成 XSS
攻击,实操如下
- 输入
<script>alert("反射型 XSS 攻击")</script>
- 确认
反射型的 XSS
攻击我曾经在一个卖民用机械的网站上见过,技术栈估计是 JSP + SpringBoot
一把梭,其中的搜索框就有这个问题
那么如何吸引用户完成这个攻击呢?其实吸引用户点击具有上面这个 url
构成的链接即可,并不需要用户亲自去输入
存储型
了解到上面反射型的 XSS
攻击,我相信你们对 XSS
攻击已经有了初步了解,被攻击的原因是没有对用户的输入做任何处理就作为 HTML
的一部分返回,所以存储型的攻击也可以以此为基础
后端渲染
后端渲染,即由后端返回 HTML
存储型 XSS
攻击原理还是一样,像服务端传输恶意代码,并且服务端会将其拼接至字符串中
被攻击网站如下
简单的 HTML
代码
<body>
评论:<span class="comment"></span>
<input class="comment-input" type="text" style="display: none" />
<br />
<button class="button" onclick="handleModifyComment()">新增评论</button>
<button
class="confirm-button"
onclick="confirmModifyComment()"
style="display: none"
>
确认
</button>
</body>
<script>
const comment = document.querySelector(".comment");
const commentInput = document.querySelector(".comment-input");
const button = document.querySelector(".button");
const confirmButton = document.querySelector(".confirm-button");
const getComment = () => {
// ..
};
const handleModifyComment = () => {
// ...
};
const confirmModifyComment = () => {
// ...
};
// 初次加载
getComment();
</script>
后端代码
router.get("/v1/user", function (req, res, next) {
const [name, setName] = useName();
res.setHeader("Content-Type", "text/html");
res.send(generateHTML(name));
});
router.get("/v1/name", function (req, res, next) {
const [name, setName] = useName();
res.send(name);
});
router.post("/v1/name", function (req, res, next) {
const [name, setName] = useName();
const { username: updateName } = req.body;
res.send(setName(updateName));
});
// ../utils/util
exports.generateHTML = (username) => {
return `
<body>
username: ${username}
</body>
`;
};
用一个闭包模拟存储(很合理吧?)
let originName = "杰尼龟";
const useName = () => {
const name = originName;
/**
* 重新赋值用户名称
* @param {string} [newName="杰尼龟"]
*/
const setName = (newName = "杰尼龟") => {
originName = newName;
return originName;
};
return [name, setName];
};
module.exports = useName;
实操如下,基于用户名称一个致命的恶意代码
<script>alert("XSS 攻击")</script>
说实话,存储型 XSS
攻击的场景出现在展示部分(比如用户的评论、用户名称、用户信息。。。)在新的网站都少了很多,展示部分的 XSS
攻击更可能出现在后端渲染的网站上,因为后端渲染是通过拼接字符串成 HTML
然后再返回
前端渲染不必多说,Vue/React/Angular
都是前端渲染的代表,它们以 JS
为基础渲染,渲染和更新通过 Ajax
来实现,而底层是通过 Node.innerText | Node.innerHTML
此类的方法渲染和更新页面
而浏览器会将通过 Node.innerText | Node.innerHTML
动态插入的内容当成普通文本,不会维护到DOM里面,XSS
攻击自然也无从谈起,因为无法执行
不过不要以为新网站没有 XSS
攻击,像 Nuxt.js
和 Next.js
这些 SSR
框架其实就是后端渲染(服务端渲染),本质上也是 HTML
拼接,不过此类开源框架往往有避免手端
前端渲染
前面说过了前后端分离的情况下,大部分数据都是通过 Ajax
拿到的,而且框架会帮我们通过 Node.innerText | Node.innerHTML
这种方式渲染,不会有 XSS
攻击的可能,但这并不意味着前端渲染并不会有被攻击的危险,比如直接渲染字符串
像 Vue
中提供了一个指令,叫 v-html
,文档里很明确的指出了具有 XSS
攻击,但像我这种文档仅供 API
参考的人,当时完全没在意,但到后来背八股文的时候发现其潜在的危险性
比如下面这个富文本的例子
像现在市面上的富文本编辑器有的可以添加链接并展示(很合理吧?富文本编辑器应该还是很常见的)
<!-- http://localhost:3000/richText.html -->
<head>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14"></script>
</head>
<body>
<div id="app">
<div>
来吧展示!
<div class="richText" v-html="richText"></div>
</div>
<br />
<button @click="addLink">添加链接</button>
</div>
</body>
<script>
const app = new Vue({
el: "#app",
data() {
return {
richText: "<h1>Hello World</h1>",
};
},
methods: {
addLink() {
const linkDialog = prompt("请输入链接", "");
if (linkDialog !== null && linkDialog !== "") {
this.richText += `<a href=${linkDialog}>一个你无法拒绝点击的链接名称</a>`;
}
},
},
});
</script>
假设啊假设啊,添加链接后会把 <a href=${linkDialog}>一个你无法拒绝点击的链接名称</a>
传入到后端,等用户下次访问 richText.html
就直接渲染后端返回的字符串,大概就是上面这个情况
如果我把链接变成下面这种
javascript:alert('XSS 攻击')
'' onclick=alert('XSS 攻击')
PS:'
表示 "
(引号), 
表示
(空格),皆为 HTML
编码,HTML
可以直接渲染,v-html
使用引号和空格需要转义,详情参考html中常见符号的代码表示
芭比Q了,被攻击了
上面的 1
和 2
两条恶意代码的渲染 DOM
结构如下
1
2
1 是利用浏览器 <a>
的 href
的特性,而 2 是利用空格间断属性,和 SQL
注入都是同根同源的操作呀!
DOM 型
阅读完反射型和存储型其实你应该已经明白,所谓 XSS
攻击就是寻找具有直接渲染和注入恶意代码的漏洞
回到 DOM
型 XSS
攻击和存储型以及反射型的区别,区别为 DOM
型 XSS
攻击为浏览器端缺陷,无需后端交互,即为前端代码的缺陷,其实也就是前端过于相信用户内容,直接渲染
而如果把存储型里的前端渲染的 XSS
攻击例子,想象成预览富文本不就是 DOM
型攻击咯(绝对不是我偷懒!)
总结
从反射型到存储型再到 DOM
型,我一开始找例子发现都是使用 JSP
的旧网站才会有 XSS
攻击漏洞,我想是不是前后端分离的现在已经不会有 XSS
攻击了,但随着我进一步深入,发现 SSR
和富文本场景也会有 XSS
攻击的可能,这些场景对我来说其实算是新兴的前端方向(至少相对于 JSP
),所以也许 XSS
攻击从未离开我们