Skip to content

06 | XSS:当你"被发送"了一条微博时,到底发生了什么?

你好,我是何为舟。 在前面的课程中,我们重点讲解了安全的一些基础知识,更多地是从宏观的层面上来谈论安全。但安全不是一个靠宏观指导就能够落地的东西。因此,接下来我会结合真实案例中的各种安全问题,来介绍具体的安全防护手段和工具。今天,我们就先从最基础的Web 安全开始。 在 Web 安全这个模块中,我们所谈论的 Web,是指所有基于 HTTP或者其他超文本传输协议(RPC 等)开发的应用,包括:网页、App、API接口等等。这类应用的共同点是:通过 HTTP等文本协议,在客户端和服务端之间进行数据交换。客户端需要将服务端传出的数据展示渲染出来,服务端需要将客户端传入的数据进行对应的处理。而Web安全所涉及的正是这些应用中存在的各类安全问题。 背景介绍完了,下面我们进入今天的正题。 基于前面安全基础知识的学习,你现在通过了面试官的考核,成功进入了这家公司。某一天,公司的网页应用中发生了一件事。 有很多用户发送了同样类型的内容,而且这些内容都是一个带有诱惑性的问题和一个可以点击的链接。这些用户全部反馈说,这不是他们自己发的。前端开发表示,用户内容都是后端产生的,他不负责。后端开发表示,这些内容都是用户自己提交上来的,他也不负责。正当大家议论纷纷的时候,你作为学习过安全专栏的人,敏锐地发现了问题的原因:这是黑客发起了XSS 攻击。 这个事情的原型,其实是 2011年微博真实出现的一次安全事件。整个事件的核心问题,其实出在这个可以点击的链接上。在这个事件中,黑客并不需要入侵到微博服务器中,只要用户点击了这个链接,就会"被发送"这样的博文。 这就是著名的 XSS 攻击所能够实现的效果。那么,XSS攻击究竟是怎么产生的呢?我们究竟该如何防护呢?今天我就带你来了解这个网页中最经典的XSS 攻击。

XSS 攻击是如何产生的?

首先,我们来看,XSS 攻击是如何产生的。作为最普遍的网页语言,HTML非常灵活,你可以在任意时候对 HTML进行修改。但是,这种灵活性也给了黑客可趁之机:通过给定异常的输入,黑客可以在你的浏览器中,插入一段恶意的JavaScript 脚本,从而窃取你的隐私信息或者仿冒你进行操作。这就是XSS攻击(Cross-SiteScripting,跨站脚本攻击)的原理。 你现在应该对 XSS 有了一个大致的了解,除此之外,你还需要了解三种 XSS攻击,它们分别是:反射型 XSS、基于 DOM 的 XSS 以及持久型XSS。下面我们一一来看。

1. 反射型 XSS

假设现在有一个搜索网页,当你输入任意一个关键词,并点击"搜索"按钮之后,这个网页就会给你展示"你搜索的结果内容是:XXX"。 我们以 PHP为例,这个网页的服务端实现逻辑如下所示: 我们可以看到,这段代码的逻辑是将搜索框输入的内容,拼接成字符串,然后填充到最终的HTML中。而且这个过程中没有任何的过滤措施,如果黑客想要对这个过程发起攻击,他会输入下面这行代码: </h3><script>alert('xss');</script><h3> 黑客输入这段字符后,网页会弹出一个告警框(我自己测试的时候,发现部分浏览器,如Safari 不会弹出告警框,这是因为浏览器自身提供了一定的 XSS保护功能)。

image.png 通过查看网页的源码,可以发现,这中间多了一段 JavaScript的脚本:

image.png 这就是我们所说的反射型 XSS攻击的过程。其实它攻击的原理很简单。我们可以总结一下,即通过开头的&lt;/h3&gt;和结尾的&lt;h3&gt;,将原本的&lt;h3&gt;标签进行闭合,然后中间通过&lt;script&gt;标签插入 JavaScript代码并执行,就完成了整个反射型 XSS的流程。 你可以注意一下浏览器的地址:http://localhost/index.php?search=<%2Fh3><script>alert('xss')%3B<%2Fscript><h3>。实际上,任何人只要点击了这个链接,就会执行一段黑客定义的 JavaScript脚本。所以,我们经常说,不要点击任何未知的链接。 反射型 XSS的总体流程我总结了一下,你可以看下面这张图。黑客诱导你点击了某个链接,这个链接提供的服务,可能就是上述的搜索功能。网页在解析到链接的参数后,执行正常的搜索逻辑,但是因为漏洞,网页中被填入了黑客定义的脚本。使得用户的浏览器,最终执行的是黑客的脚本。

image.png

2. 基于 DOM 的 XSS

在上面的例子中我们可以看到,反射型 XSS产生在前后端一体的网页应用中,服务端逻辑会改变最终的网页代码。但是,目前更流行的其实是前后端分离,这样网页的代码不会受服务端影响。那么,这样是不是就安全了呢? 显然不是的。尽管服务端无法改变网页代码,但网页本身的 JavaScript仍然可以改变。而黑客只要利用了这一点,同样能够在网页中插入自己的脚本。这也就是所谓的基于DOM 的 XSS 漏洞。 对于上述搜索功能,通过前后端分离,它的源码就变成了下面这样: 这段代码能够实现和之前的 PHP代码相同的逻辑:当你在搜索框点击搜索关键词之后,网页会展示你输入的关键词。只不过,HTML是通过 JavaScript 脚本修改DOM来实现这个功能的。 那么和上述例子一样,在基于 DOM 的 XSS中,黑客也可以通过插入一段&lt;script&gt;alert('xss');&lt;/script&gt;来执行指定的 JavaScript脚本。基于 DOM 的 XSS 总体流程如下图所示。可以看到,这个流程其实和反射型XSS一致,只是不需要经过服务端了而已。

image.png

3. 持久型 XSS

你可以回想一下,当你在网页中搜索一个关键词时,实际上与这个关键词相关的所有搜索结果都会被展示出来。一旦这些搜索结果中,包含黑客提供的某个恶意JavaScript脚本,那么只要我们浏览了这个网页,就有可能会执行这些脚本。这就是持久型XSS。因为这些恶意的搜索结果,会长期保存在服务端数据库中,所以它又叫作存储型XSS。在应用中,存储用户的输入并对它们进行展示的地方,都可能出现持久型XSS。比如:搜索结果、评论、博文等等。 有了前面的铺垫,持久型 XSS的产生过程就很好理解了,具体我就不细说了,我还是把总体流程画了一张图,你可以仔细看看。

image.png 相比前面两种 XSS 攻击来说,持久型 XSS往往具备更强的危害性。因为对于一个反射型或者基于 DOM 的 XSS来说,需要黑客诱导用户点击恶意的 URL,才能够成功地在用户浏览器上执行JavaScript脚本。这对黑客在诱导用户操作方面的能力提出了考验:并不是所有的用户都是小白,一些有经验的用户会在点击链接前进行一定的考虑。 而持久型 XSS 则不同,它是将恶意的 JavaScript脚本写入到了正常的服务端数据库中,因此,只要用户正常的使用业务功能,就会被注入JavaScript 脚本。所以说,持久型 XSS在传播速度和传播范围上,会远远超出其他类型的XSS。

通过 XSS 攻击,黑客能做什么?

我们知道,这 3 种 XSS 攻击,都是因为黑客在用户的浏览器中执行了恶意的JavaScript 脚本。那么执行这些 JavaScript脚本有什么样的危害呢?我把这些危害总结了一下,可以分为下面几种。

从上面的例子中,我们可以看到,黑客可以窃取用户的Cookie。因为黑客注入的 JavaScript 代码是运行在 server.com这个域名下的,因此,黑客可以在 JavaScript 中通过 document.cookie 获得Cookie 信息。 另外,需要我们注意的是,受SOP(Same OriginPolicy,同源策略)保护,我们在 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"的漏洞,因为 PHP无法处理某些编码的字符而导致崩溃。 因此,我更推荐在需要输出的时候去进行验证,即当需要展示的时候,我们再对内容进行验证,这样我们就能够根据不同的环境去采取不同的保护方案了。 在 HTML 中,常见的 XSS注入点我已经总结好了,你可以看下面这个表格:

image.png

2. 编码

现在,我们已经理解了,XSS防护的核心原则就是验证,那具体该怎么去做验证呢?我认为,我们可以优先采用编码的方式来完成。所谓编码,就是将部分浏览器识别的关键词进行转换(比如< 和 >),从而避免浏览器产生误解。对于客户端来说,编码意味着,使用JavaScript提供的功能对用户内容进行处理。具体的方法我也总结了一下,你可以看这个表格。

image.png 对于最后一个注入点,即在 JavaScript中进行注入,目前还没有内置的编码方式来对它提供保护。你当然可以通过诸如URL编码等方式进行编码,但这有可能对应用的自身逻辑产生影响。因此,JavaScript中的注入并不适合通过编码来进行保护。

3. 检测和过滤

但是,在很多时候,编码会对网页实际的展现效果产生影响。比如,原本用户可能想展示一个1>0,却被编码展示成了1&gt0。尽管网络环境安全了,却对用户造成了困扰。那么,我们还可以采取哪些方法进行验证呢?接下来我就为你介绍一下检测和过滤。 首先,我们需要对用户的内容进行检测。在这里,我们可以采用黑名单和白名单的规则。黑名单往往是我们最直接想到的方法:既然黑客要插入&lt;javascript&gt;标签,那么我们就检测用户内容中是否存在&lt;javascript&gt;标签就好了。 但是,黑客的攻击方法是无穷无尽的。你检测了&lt;javascript&gt;,黑客就可以改成&lt;JavaScript&gt;(因为 HTML标签对大小写不敏感),甚至有些时候还能够编码成&#106;avascript等等。另外,HTML5的发展速度很快,总是有新的标签被开发出来,这些新标签中也可能包含新的注入点。因此,黑名单的更新和维护过程,是需要我们和黑客进行长期对抗的过程 所以,在检测中,我更推荐使用白名单的规则。因为白名单的规则比较简单,并且十分有效。比如,在只输入一个分数的地方,规定只有整型变量是合法的。这样一来,你就能够检测出99.99% 的攻击行为了。 说完了检测,那当发现某个用户的内容可能存在 XSS攻击脚本时,我们该怎么处理呢?这个时候,处理选项有两个:拒绝或者过滤。毫无疑问,拒绝是最安全的选项。一旦你发现可能的XSS攻击脚本,只要不将这段用户内容展现出来,就能避免可能的攻击行为。 但是,拒绝会阻碍用户的使用流程,从用户体验的角度上来考虑的话,过滤会更被用户所接受。上面提到的编码就属于一种过滤的方式。除此之外,我们也可以直接对敏感字符进行替换删除等。需要注意的是,在替换的时候,一定不能采取黑名单的形式(比如:将javascript 进行删除,那黑客就可以通过 JavaScript来绕过),而是应该采取白名单的形式(比如,除了 div之外的标签全部删除)。 同样地,过滤的流程也必须彻底。比如,我看到过有人采用下面这行字符串来过滤 javascript标签: $str=str_replace('&lt;javascript&gt;','',$str); 但黑客只需要将 str 的值变成&lt;java&lt;javascript&gt;script&gt;就可以了,因为str_replace('&lt;javascript&gt;','','&lt;java&lt;javascript&gt;script&gt;')的结果就是&lt;javascript&gt;

4.CSP

面对 XSS 这样一个很普遍的问题,W3C 提出了 CSP(Content SecurityPolicy,内容安全策略)来提升 Web 的安全性。所谓 CSP,就是在服务端返回的HTTP header 里面添加一个 Content-Security-Policy选项,然后定义资源的白名单域名。浏览器就会识别这个字段,并限制对非白名单资源的访问。 配置样例如下所示: 那我们为什么要限制外域资源的访问呢?这是因为 XSS通常会受到长度的限制,导致黑客无法提交一段完整的 JavaScript代码。为了解决这个问题,黑客会采取引用一个外域 JavaScript资源的方式来进行注入。除此之外,限制了外域资源的访问,也就限制了黑客通过资源请求的方式,绕过SOP 发送 GET 请求。目前,CSP还是受到了大部分浏览器支持的,只要用户使用的是最新的浏览器,基本都能够得到很好的保护。

总结

好了,我们讲了 XSS的攻击类型、会产生的影响,以及如何对它进行防护。下面我来带你总结回顾一下,你要掌握的重点内容。 简单来说,XSS 就是利用 Web 漏洞,在用户的浏览器中执行黑客定义的JavaScript 脚本,这样一种攻击方式。根据攻击方式的不同,可以分为:反射型XSS、基于 DOM 的 XSS 和存储型XSS。通过在用户的浏览器中注入脚本,黑客可以通过各种方式,采集到用户的敏感信息,包括:Cookie、按键记录、密码等。 预防 XSS主要通过对用户内容的验证来完成。首先,我推荐在需要展示用户内容的时候去进行验证,而不是当用户输入的时候就去验证。在验证过程中,我们优先采用编码的方式来完成。如果编码影响到了业务的正常功能,我们就可以采用白名单的检测和过滤方式来进行验证。除此之外,我们可以根据业务需要,配置合适的CSP 规则,这也能在很大程度上降低 XSS产生的影响。 另外,在这里,我把本节课的重点内容梳理了一个脑图。你可以根据它来查漏补缺,加深印象。

image.png

思考题

好了,通过今天的学习,相信你已经了解了什么是 XSS攻击。你可以试着分析一下,文章开头提到的事件中,黑客是利用哪种类型的 XSS发起的攻击呢?我们应该怎么进行防御呢? 另外,在事件中我也描述了开发"甩锅"的场景:前端也好、后端也好,开发人员都不认为是自己的问题。那么,你认为出现这种安全事件,应该由谁来"背锅"呢?是开发、运维还是安全负责人呢? 欢迎留言和我分享你的思考和疑惑,也欢迎你把文章分享给你的朋友。我们下一讲再见!

用心记录,持续成长