前端安全——XSS攻击与防御原理详解
前⾔
这周末应该有很多⼈被叫去加班改bug了吧,因为出现了⼀个log4j的bug,可以让⿊客进⾏攻击,所以都在紧急修复这个问题。
现在互联⽹的开发者最关⼼的问题是是什么?肯定是安全。不⽌是开发者会关⼼安全问题,就是普通⽤户也会关⼼这个问题,如果⼀个⽹站或者⼀个APP不安全,⽤户是不会选择使⽤的,谁愿意让⾃⼰的信息公布于众,⽽随着技术的发展各种各样的攻击⼿段层出不穷。这⾥有⼀个⽹站,隔⼀段时间就会统计各种web攻击的排名前⼗:,感兴趣的可以点进去看看,下⾯我先列出2017年和2021年攻击⼿段排名
top10:
从图上可以看到作为⼀名前端开发者经常听到的⼀些攻击:XSS、Injection等等,为啥这⾥⾯没有CSRF呢,⽬前为⽌我们对CSRF的防范已经很成熟了,现在使⽤的token就是为了防⽌CSRF的攻击⽽产⽣,⽽且它的攻击⼿段简单,不像XSS难以防范,所以已经跌出前⼗了,在前⼏年还能排进前⼗。
XSS
下⾯就开始讲我们本篇⽂章的重点了,那就是XSS(跨站脚本攻击)。先来看⼀下XSS的定义:XSS攻击通常指的是通过利⽤⽹页开发时留下的漏洞,通过巧妙的⽅法注⼊恶意指令代码到⽹页,使⽤户加载并执⾏攻击者恶意制造的⽹页程序。这些恶意⽹页程序通常是JavaScript,但实际上也可以包括Java、 VBScript、ActiveX、 Flash 或者甚⾄是普通的HTML。攻击成功后,攻击者可能得到包括但不限于更⾼的权限(如执⾏⼀些操作)、私密⽹页内容、会话和cookie等各种内容。
我们想个问题:如果你能够在别⼈的⽹站⾥植⼊JS,你会⼲嘛?如果我们可以在⼀个⽹站⾥植⼊⾃⼰的js代码,我们就拥有了⽆限的可能,⽐如:⽆限弹窗、篡改⽹站页⾯、获取⽤户信息等等。甚⾄你可以在⽹站⾥植⼊⼴告,以此来获取⼤量利润,这都是完全可以实现的。所以XSS的危害是巨⼤的,作为⼀个前端开发⼈员不得不⼩⼼。
XSS分类
根据攻击的来源可以分为三种类型:存储型、反射型、DOM型三种。
存储型XSS
存储型 XSS 的攻击步骤:
1. 攻击者将恶意代码提交到⽬标⽹站的数据库中。
2. ⽤户打开⽬标⽹站时,⽹站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
3. ⽤户浏览器接收到响应后解析执⾏,混在其中的恶意代码也被执⾏。
html document是什么
4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者冒充⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。
5. 这种攻击常见于带有⽤户保存数据的⽹站功能,如论坛发帖、商品评论、⽤户私信等。
反射型XSS
反射型 XSS 的攻击步骤:
1. 攻击者构造出特殊的 URL,其中包含恶意代码。
2. ⽤户打开带有恶意代码的 URL 时,⽹站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
3. ⽤户浏览器接收到响应后解析执⾏,混在其中的恶意代码也被执⾏。
4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者冒充⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。
反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库⾥,反射型 XSS 的恶意代码存在 URL ⾥。
反射型 XSS 漏洞常见于通过 URL 传递参数的功能,如⽹站搜索、跳转等。
由于需要⽤户主动打开恶意的 URL 才能⽣效,攻击者往往会结合多种⼿段诱导⽤户点击。
POST 的内容也可以触发反射型 XSS,只不过其触发条件⽐较苛刻(需要构造表单提交页⾯,并引导⽤户点击),所以⾮常少见。
DOM型XSS
1. 攻击者构造出特殊的 URL,其中包含恶意代码。
2. ⽤户打开带有恶意代码的 URL。
3. ⽤户浏览器接收到响应后解析执⾏,前端 JavaScript 取出 URL 中的恶意代码并执⾏。
4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者冒充⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。
DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执⾏恶意代码由浏览器端完成,属于前端 JavaScript ⾃⾝的安全漏洞,⽽其他两种 XSS 都属于服务端的安全漏洞。
攻击举例
随便在⼀个html⽂件⾥写⼀个input和button,假设现在是⼀个⽂章的评论区,那么我们在这个input框⾥输⼊的⽂字就是我们要评论的内容,点击按钮发表评论,发表的内容会在下⾯展⽰出来,我们先测试⼀下:
很完美,可以打印出来了,那么普通⽂本可以展⽰出来,换成标签呢,我们在试试:
可以看到我们输⼊s标签就是加横线的,下⾯打印出来的和我们输⼊的不⼀样,这⾥我是使⽤的innerHTML展⽰的输⼊框内容。如果我们在⼀个⽹站⾥输⼊东西之后展⽰的是这样的结果,那么我们就可以做很多事情了,因为这代表着这个⽹站没有对XSS进⾏防御,我们可以为所欲为,下⾯就进⾏⼀个弹窗的测试,更直观的展⽰使⽤document.write()来实现,innerHTML需要⼀些特殊操作:
如上图所⽰,在输⼊框中输⼊⼀⾏js代码,点击打印⽂字就会执⾏。不要仅仅以为只能执⾏这⼏⾏js代码感觉做不了什么事情,script标签的src属性可以链接外部⽂件。加⼊⼀个⿊客写了⼀堆代码,然后使⽤src加载这个⽂件并执⾏,这是⼀件⾮常可怕的事情。上⾯的演⽰只是本地模拟,真正的场景是会把评论内容存进数据库,这样会造成每个看到这个评论的⽤户都会执⾏这句恶意注⼊的代码,这就会造成很严重的影响,下⾯我们再来看⼀个改变DOM结构的XSS攻击:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text" id="inp">
<button id="btn">打印⽂字</button>
<div id="box"></div>
<script>
let inp = ElementById('inp');
let btn = ElementById('btn');
let box = ElementById('box');
let value = inp.value;
box.innerHTML ="<img src='"+value+"'></img>";
}
</script>
</body>
</html>
这是我们要测试的代码,可以看到这⾏代码⾥引⼊了⼀个img标签,输⼊的内容会作为这个img的src,这个场景也很常见,⽐如头像上传,图⽚上传等等。
我们在input框中输⼊1'onerror='alert("xss")也会执⾏这些代码,1’是为了闭合img标签的src,否则后⾯的js代码⽆法⽣效。我们甚⾄可以在输⼊框中执⾏⼀个函数添加⼀个诱导链接,然后获取⽤户信息。
XSS防御
通过上⾯的举例和分类可以看出xss攻击有两⼤要素:
1. 攻击者提交恶意代码
2. 浏览器执⾏恶意代码
解决⽅法:从输⼊到输出都要进⾏过滤、转义。永远不要相信⽤户的输⼊
输⼊
1. ⽤户输⼊
2. url参数
3. post参数
我们需要对⼀些特殊的字符、标签进⾏编码展⽰:
输出
永远不要相信innerHTML、document.write()、eval这三兄弟,只要⽹站中使⽤了这三种输出⽅式,那么恭喜你,喜提XSS攻击,这三种输出⽅式可以做太多的事情。
CSP(Content Security Policy)
严格的 CSP 在 XSS 的防范中可以起到以下的作⽤:
1. 禁⽌加载外域代码,防⽌复杂的攻击逻辑。
2. 禁⽌外域提交,⽹站被攻击后,⽤户的数据不会泄露到外域。
3. 禁⽌内联脚本执⾏(规则较严格,⽬前发现 GitHub 使⽤)。
4. 禁⽌未授权的脚本执⾏(新特性,Google Map 移动版在使⽤)。
5. 合理使⽤上报可以及时发现 XSS,利于尽快修复问题。
后记
⼤家在进⾏⽹站开发时⼀定不要盲⽬⾃信,尽量使⽤完善的防御⼯具或者防御库,在开发完成后请测试⼈员或者安全⼈员进⾏测试,确保没有问题后在进⾏上线,否则可能会带来⼀系列的灾难。
最后给⼤家提供⼀个XSS⼩游戏,⼀共有6关,都是实现XSS攻击的,每完成⼀个攻击就会进⼊下⼀关,要是通关了,我想你开发的⽹站在XSS防御⽅⾯应该是⽐较可靠的: