You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

225 lines
19 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 06 | XSS当你“被发送”了一条微博时到底发生了什么
你好,我是何为舟。
在前面的课程中,我们重点讲解了安全的一些基础知识,更多地是从宏观的层面上来谈论安全。但**安全不是一个靠宏观指导就能够落地的东西**。因此接下来我会结合真实案例中的各种安全问题来介绍具体的安全防护手段和工具。今天我们就先从最基础的Web安全开始。
在Web安全这个模块中我们所谈论的Web是指所有基于HTTP或者其他超文本传输协议RPC等开发的应用包括网页、App、API接口等等。这类应用的共同点是通过HTTP等文本协议在客户端和服务端之间进行数据交换。客户端需要将服务端传出的数据展示渲染出来服务端需要将客户端传入的数据进行对应的处理。而Web安全所涉及的正是这些应用中存在的各类安全问题。
背景介绍完了,下面我们进入今天的正题。
基于前面安全基础知识的学习,你现在通过了面试官的考核,成功进入了这家公司。某一天,公司的网页应用中发生了一件事。
有很多用户发送了同样类型的内容而且这些内容都是一个带有诱惑性的问题和一个可以点击的链接。这些用户全部反馈说这不是他们自己发的。前端开发表示用户内容都是后端产生的他不负责。后端开发表示这些内容都是用户自己提交上来的他也不负责。正当大家议论纷纷的时候你作为学习过安全专栏的人敏锐地发现了问题的原因这是黑客发起了XSS攻击。
这个事情的原型其实是2011年微博真实出现的一次安全事件。整个事件的核心问题其实出在这个可以点击的链接上。在这个事件中黑客并不需要入侵到微博服务器中只要用户点击了这个链接就会“被发送”这样的博文。
这就是著名的XSS攻击所能够实现的效果。那么XSS攻击究竟是怎么产生的呢我们究竟该如何防护呢今天我就带你来了解这个网页中最经典的XSS攻击。
## XSS攻击是如何产生的
首先我们来看XSS攻击是如何产生的。作为最普遍的网页语言HTML非常灵活你可以在任意时候对HTML进行修改。但是这种灵活性也给了黑客可趁之机通过给定异常的输入黑客可以在你的浏览器中插入一段恶意的JavaScript脚本从而窃取你的隐私信息或者仿冒你进行操作。这就是**XSS攻击**Cross-Site Scripting跨站脚本攻击的原理。
你现在应该对XSS有了一个大致的了解除此之外你还需要了解三种XSS攻击它们分别是反射型XSS、基于DOM的XSS以及持久型XSS。下面我们一一来看。
### 1.反射型XSS
假设现在有一个搜索网页当你输入任意一个关键词并点击“搜索”按钮之后这个网页就会给你展示“你搜索的结果内容是XXX”。
![](https://static001.geekbang.org/resource/image/a8/63/a80e92776bad0f35e8d7030806165163.jpg)
我们以PHP为例这个网页的服务端实现逻辑如下所示
```
<!DOCTYPE html>
<html>
<body>
<form role="search" action="" method="GET">
<input type="text" name="search" placeholder="请输入要搜索的内容">
<button type="submit">搜索</button>
</form>
<?php
if (isset($_GET['search']) && !empty($_GET['search'])) {
$search = $_GET['search'];
echo "<h3>你搜索的结果内容是:" . $search . "</h3>";
}
?>
</body>
</html>
```
我们可以看到这段代码的逻辑是将搜索框输入的内容拼接成字符串然后填充到最终的HTML中。而且这个过程中没有任何的过滤措施如果黑客想要对这个过程发起攻击他会输入下面这行代码
```
</h3><script>alert('xss');</script><h3>
```
黑客输入这段字符后网页会弹出一个告警框我自己测试的时候发现部分浏览器如Safari不会弹出告警框这是因为浏览器自身提供了一定的XSS保护功能
![](https://static001.geekbang.org/resource/image/f0/69/f0d2d16b482c9bbc5388250f2d34cc69.jpg)
通过查看网页的源码可以发现这中间多了一段JavaScript的脚本
![](https://static001.geekbang.org/resource/image/29/98/29cff8464c5dd7b88dafb377e0439b98.jpg)
这就是我们所说的反射型XSS攻击的过程。其实它攻击的原理很简单。我们可以总结一下即通过开头的`</h3>`和结尾的`<h3>`,将原本的`<h3>`标签进行闭合,然后中间通过`<script>`标签插入JavaScript代码并执行就完成了整个反射型XSS的流程。
你可以注意一下浏览器的地址:[http://localhost/index.php?search=<%2Fh3><script>alert('xss')%3B<%2Fscript><h3>](http://localhost/index.php?search=%3C%2Fh3%3E%3Cscript%3Ealert%28%27xss%27%29%3B%3C%2Fscript%3E%3Ch3%3E) 。实际上任何人只要点击了这个链接就会执行一段黑客定义的JavaScript脚本。所以我们经常说不要点击任何未知的链接。
反射型XSS的总体流程我总结了一下你可以看下面这张图。黑客诱导你点击了某个链接这个链接提供的服务可能就是上述的搜索功能。网页在解析到链接的参数后执行正常的搜索逻辑但是因为漏洞网页中被填入了黑客定义的脚本。使得用户的浏览器最终执行的是黑客的脚本。
![](https://static001.geekbang.org/resource/image/b8/2c/b85f24cbc8243426fb270bcb74be682c.jpeg)
### 2.基于DOM的XSS
在上面的例子中我们可以看到反射型XSS产生在前后端一体的网页应用中服务端逻辑会改变最终的网页代码。但是目前更流行的其实是前后端分离这样网页的代码不会受服务端影响。那么这样是不是就安全了呢
显然不是的。尽管服务端无法改变网页代码但网页本身的JavaScript仍然可以改变。而黑客只要利用了这一点同样能够在网页中插入自己的脚本。这也就是所谓的基于DOM的XSS漏洞。
对于上述搜索功能,通过前后端分离,它的源码就变成了下面这样:
```
<!DOCTYPE html>
<html>
<body>
<form role="search" action="" method="GET">
<input type="text" name="search" placeholder="请输入要搜索的内容">
<button type="submit">搜索</button>
</form>
<script>
var search = location.search.substring(8);
document.write('你搜索的结果内容是:' + decodeURIComponent(search));
</script>
</body>
</html>
```
这段代码能够实现和之前的PHP代码相同的逻辑当你在搜索框点击搜索关键词之后网页会展示你输入的关键词。只不过HTML是通过JavaScript脚本修改[DOM](https://baike.baidu.com/item/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/1033822?fromtitle=DOM&fromid=50288&fr=aladdin)来实现这个功能的。
那么和上述例子一样在基于DOM的XSS中黑客也可以通过插入一段`<script>alert('xss');</script>`来执行指定的JavaScript脚本。基于DOM的XSS总体流程如下图所示。可以看到这个流程其实和反射型XSS一致只是不需要经过服务端了而已。
![](https://static001.geekbang.org/resource/image/ac/bf/ac0ce9c0f42db7dd48cb155228b7b1bf.jpeg)
### 3.持久型XSS
你可以回想一下当你在网页中搜索一个关键词时实际上与这个关键词相关的所有搜索结果都会被展示出来。一旦这些搜索结果中包含黑客提供的某个恶意JavaScript脚本那么只要我们浏览了这个网页就有可能会执行这些脚本。这就是持久型XSS。因为这些恶意的搜索结果会长期保存在服务端数据库中所以它又叫作存储型XSS。在应用中存储用户的输入并对它们进行展示的地方都可能出现持久型XSS。比如搜索结果、评论、博文等等。
有了前面的铺垫持久型XSS的产生过程就很好理解了具体我就不细说了我还是把总体流程画了一张图你可以仔细看看。
![](https://static001.geekbang.org/resource/image/2e/75/2e8c429f46837b3f46a276e462c93175.jpeg)
相比前面两种XSS攻击来说持久型XSS往往具备更强的危害性。因为对于一个反射型或者基于DOM的XSS来说需要黑客诱导用户点击恶意的URL才能够成功地在用户浏览器上执行JavaScript脚本。这对黑客在诱导用户操作方面的能力提出了考验并不是所有的用户都是小白一些有经验的用户会在点击链接前进行一定的考虑。
而持久型XSS则不同它是将恶意的JavaScript脚本写入到了正常的服务端数据库中因此只要用户正常的使用业务功能就会被注入JavaScript脚本。所以说持久型XSS在传播速度和传播范围上会远远超出其他类型的XSS。
## 通过XSS攻击黑客能做什么
我们知道这3种XSS攻击都是因为黑客在用户的浏览器中执行了恶意的JavaScript脚本。那么执行这些JavaScript脚本有什么样的危害呢我把这些危害总结了一下可以分为下面几种。
### 1.窃取Cookie
从上面的例子中我们可以看到黑客可以窃取用户的Cookie。因为黑客注入的JavaScript代码是运行在server.com这个域名下的因此黑客可以在JavaScript中通过document.cookie获得Cookie信息。
另外,需要我们注意的是,受[SOP](https://baike.baidu.com/item/%E5%90%8C%E6%BA%90%E7%AD%96%E7%95%A5/3927875?fr=aladdin)Same Origin Policy同源策略保护我们在server.com中是无法直接向hacker.com发送GET或者POST请求的。这也是为什么在上面的例子中我们需要通过window.location来执行跳转操作间接地将Cookie信息发送出去。除了window.location之外我们还可以通过加载JavaScript文件、图片等方式向attacker.com发送带有Cookie的GET请求。
### 2.未授权操作
除了窃取敏感信息以外黑客还可以利用JavaScript的特性直接代替用户在HTML进行各类操作。
在文章开头我们提到的微博XSS攻击事件中黑客就利用JavaScript脚本让用户发送了一个微博微博中同时还带有反射型XSS的链接。这样一来每个点击链接的用户都会通过微博的形式诱导更多的用户点击链接一传十、十传百造成大范围的传播。
### 3.按键记录和钓鱼
窃取Cookie和未授权操作都是我们很容易想到的危害除此之外JavaScript还能做什么呢
JavaScript的功能十分强大它还能够记录用户在浏览器中的大部分操作。比如鼠标的轨迹、键盘输入的信息等。也就是说你输入的账号名和密码都可以被JavaScript记录下来从而被黑客获取到。
另外即使某个存在XSS漏洞的页面不具备任何输入框黑客还可以通过修改DOM伪造一个登录框来诱导用户在本不需要登录的页面去输入自己的用户名和密码。这也是“钓鱼”的一种形式在这个过程中用户访问的域名是完全正常的只是页面被篡改了所以具备更高的迷惑性。
## 如何进行XSS防护
认识到XSS的危害之后作为开发人员我们最应该掌握的是如何避免在开发过程中出现XSS漏洞。接下来我们就来看一看具体有哪些防护方法。
### 1.验证输入OR验证输出
防护的核心原则是:**一切用户输入皆不可信**。你的第一反应一定是,这很好实现啊,当接收到用户的输入时,我们就进行验证,这不就做到了吗?实际上并不是这么简单的,我们还是通过搜索这个例子来看。在用户点击“搜索”按钮之后,如果我们马上对他输入的内容进行验证,这样就会产生两个问题。
1.你将无法保存用户的原始输入信息。这样一来当出现了Bug或者想要对黑客行为进行溯源时你只能“推断”而不能准确地获取用户的原始输入。
2.用户的内容可能会被多种语言获取和使用提前编码或者处理将产生未知的问题。比如在旧版本的PHP中就存在“[magic quotes](https://www.php.net/manual/en/security.magicquotes.php)”的漏洞因为PHP无法处理某些编码的字符而导致崩溃。
**因此,我更推荐在需要输出的时候去进行验证**,即当需要展示的时候,我们再对内容进行验证,这样我们就能够根据不同的环境去采取不同的保护方案了。
在HTML中常见的XSS注入点我已经总结好了你可以看下面这个表格
![](https://static001.geekbang.org/resource/image/7c/a0/7c1b9b3da33247963c2c1bf38ae184a0.jpeg)
### 2.编码
现在,我们已经理解了,**XSS防护的核心原则就是验证**,那具体该怎么去做验证呢?我认为,我们可以优先采用编码的方式来完成。所谓编码,就是将部分浏览器识别的关键词进行转换(比如<和>从而避免浏览器产生误解。对于客户端来说编码意味着使用JavaScript提供的功能对用户内容进行处理。具体的方法我也总结了一下你可以看这个表格。
![](https://static001.geekbang.org/resource/image/ca/1e/ca06351dcf763b86bb8bef554763bc1e.jpeg)
对于最后一个注入点即在JavaScript中进行注入目前还没有内置的编码方式来对它提供保护。你当然可以通过诸如URL编码等方式进行编码但这有可能对应用的自身逻辑产生影响。因此JavaScript中的注入并不适合通过编码来进行保护。
### 3.检测和过滤
但是在很多时候编码会对网页实际的展现效果产生影响。比如原本用户可能想展示一个1>0却被编码展示成了1&gt0。尽管网络环境安全了却对用户造成了困扰。那么我们还可以采取哪些方法进行验证呢接下来我就为你介绍一下检测和过滤。
首先,我们需要对用户的内容进行检测。在这里,我们可以采用黑名单和白名单的规则。黑名单往往是我们最直接想到的方法:既然黑客要插入`<javascript>`标签,那么我们就检测用户内容中是否存在`<javascript>`标签就好了。
但是,黑客的攻击方法是无穷无尽的。你检测了`<javascript>`,黑客就可以改成`<JavaScript>`因为HTML标签对大小写不敏感甚至有些时候还能够编码成`&#106;avascript`等等。另外HTML5的发展速度很快总是有新的标签被开发出来这些新标签中也可能包含新的注入点。因此黑名单的更新和维护过程是需要我们和黑客进行长期对抗的过程
所以,在检测中,**我更推荐使用白名单的规则**。因为白名单的规则比较简单并且十分有效。比如在只输入一个分数的地方规定只有整型变量是合法的。这样一来你就能够检测出99.99%的攻击行为了。
说完了检测那当发现某个用户的内容可能存在XSS攻击脚本时我们该怎么处理呢这个时候处理选项有两个拒绝或者过滤。毫无疑问拒绝是最安全的选项。一旦你发现可能的XSS攻击脚本只要不将这段用户内容展现出来就能避免可能的攻击行为。
但是拒绝会阻碍用户的使用流程从用户体验的角度上来考虑的话过滤会更被用户所接受。上面提到的编码就属于一种过滤的方式。除此之外我们也可以直接对敏感字符进行替换删除等。需要注意的是在替换的时候一定不能采取黑名单的形式比如将javascript进行删除那黑客就可以通过JavaScript来绕过而是**应该采取白名单的形式**比如除了div之外的标签全部删除
同样地,**过滤的流程也必须彻底**。比如我看到过有人采用下面这行字符串来过滤javascript标签
```
$str=str_replace('<javascript>','',$str);
```
但黑客只需要将str的值变成`<java<javascript>script>`就可以了,因为`str_replace('<javascript>','','<java<javascript>script>')`的结果就是`<javascript>`。
### 4.CSP
面对XSS这样一个很普遍的问题W3C提出了CSPContent Security Policy内容安全策略来提升Web的安全性。所谓CSP就是在服务端返回的HTTP header里面添加一个Content-Security-Policy选项然后定义资源的白名单域名。浏览器就会识别这个字段并限制对非白名单资源的访问。
配置样例如下所示:
```
Content-Security-Policy:default-src none; script-src self;
connect-src self; img-src self; style-src self;
```
那我们为什么要限制外域资源的访问呢这是因为XSS通常会受到长度的限制导致黑客无法提交一段完整的JavaScript代码。为了解决这个问题黑客会采取引用一个外域JavaScript资源的方式来进行注入。除此之外限制了外域资源的访问也就限制了黑客通过资源请求的方式绕过SOP发送GET请求。目前CSP还是受到了大部分浏览器支持的只要用户使用的是最新的浏览器基本都能够得到很好的保护。
## 总结
好了我们讲了XSS的攻击类型、会产生的影响以及如何对它进行防护。下面我来带你总结回顾一下你要掌握的重点内容。
简单来说XSS就是利用Web漏洞在用户的浏览器中执行黑客定义的JavaScript脚本这样一种攻击方式。根据攻击方式的不同可以分为反射型XSS、基于DOM的XSS和持久型XSS。通过在用户的浏览器中注入脚本黑客可以通过各种方式采集到用户的敏感信息包括Cookie、按键记录、密码等。
预防XSS主要通过对用户内容的验证来完成。首先我推荐在需要展示用户内容的时候去进行验证而不是当用户输入的时候就去验证。在验证过程中我们优先采用编码的方式来完成。如果编码影响到了业务的正常功能我们就可以采用白名单的检测和过滤方式来进行验证。除此之外我们可以根据业务需要配置合适的CSP规则这也能在很大程度上降低XSS产生的影响。
另外,在这里,我把本节课的重点内容梳理了一个脑图。你可以根据它来查漏补缺,加深印象。
![](https://static001.geekbang.org/resource/image/48/20/48a923998b80ad2f1c2a274704690e20.jpg)
## 思考题
好了通过今天的学习相信你已经了解了什么是XSS攻击。你可以试着分析一下文章开头提到的事件中黑客是利用哪种类型的XSS发起的攻击呢我们应该怎么进行防御呢
另外,在事件中我也描述了开发“甩锅”的场景:前端也好、后端也好,开发人员都不认为是自己的问题。那么,你认为出现这种安全事件,应该由谁来“背锅”呢?是开发、运维还是安全负责人呢?
欢迎留言和我分享你的思考和疑惑,也欢迎你把文章分享给你的朋友。我们下一讲再见!