Overview
方法 setDestination() 向 Web 浏览器发送非法数据,从而导致浏览器执行恶意代码。
Details
Cross-Site Scripting (XSS) 漏洞在以下情况下发生:
- 数据通过一个不可信赖的数据源进入 Web 应用程序。对于基于 DOM 的 XSS,将从 URL 参数或浏览器中的其他值读取数据,并使用客户端代码将其重新写入该页面。对于 Reflected XSS,不可信赖的数据源通常为 Web 请求,而对于 Persisted(也称为 Stored)XSS,该数据源通常为数据库或其他后端数据存储。
2.未经验证但包含在动态内容中的数据将传送给 Web 用户。对于基于 DOM 的 XSS,任何时候当受害人的浏览器解析 HTML 页面时,恶意内容都将作为 DOM(文档对象模型)创建的一部分执行。
传送到 Web 浏览器的恶意内容通常采用 JavaScript 片段的形式,但也可能会包含一些 HTML、Flash 或者其他任意一种可以被浏览器执行的代码。基于 XSS 的攻击手段花样百出,几乎是无穷无尽的,但通常它们都会包含传输给攻击者的私有数据(如 Cookie 或者其他会话信息)。在攻击者的控制下,指引受害者进入恶意的网络内容;或者利用易受攻击的站点,对用户的机器进行其他恶意操作。
**例 1:**下面的 JavaScript 代码片段可从 URL 中读取雇员 ID eid,并将其显示给用户。
<SCRIPT> var pos=document.URL.indexOf("eid=")+4;
document.write(document.URL.substring(pos,document.URL.length));
</SCRIPT>
**示例 2:**考虑使用 HTML 表单:
<div id="myDiv"> Employee ID: <input type="text" id="eid"><br> ... <button>Show results</button> </div> <div id="resultsDiv"> ... </div>
下面的 jQuery 代码片段可从表单中读取雇员 ID,并将其显示给用户。
$(document).ready(function(){ $("#myDiv").on("click", "button", function(){ var eid = $("#eid").val(); $("resultsDiv").append(eid); ... }); });
如果文本输入中 ID 为 eid 的雇员 ID 仅包含标准字母数字文本,则这些代码示例可正确运行。如果 eid 中的某个值包含元字符或源代码,则 Web 浏览器就会在显示 HTTP 响应时执行该代码。
**示例 3:**以下代码显示了 React 应用程序中基于 DOM 的 XSS 示例:
let element = JSON.parse(getUntrustedInput()); ReactDOM.render(<App> {element} </App>);
在Example 3 中,如果攻击者可以控制从 getUntrustedInput() 检索到的整个 JSON 对象,他们可能就能够使 React 将 element 呈现为一个组件,从而可以使用他们自己控制的值传递具有 dangerouslySetInnerHTML 的对象,这是一种典型的 Cross-Site Scripting 攻击。 最初,这些代码看起来似乎不会轻易遭受攻击。毕竟,有谁会输入包含可在自己电脑上运行的恶意代码的内容呢?真正的危险在于攻击者会创建恶意的 URL,然后采用电子邮件或社交工程的欺骗手段诱使受害者访问此 URL 的链接。当受害者单击这个链接时,他们不知不觉地通过易受攻击的网络应用程序,将恶意内容带到了自己的电脑中。这种对易受攻击的 Web 应用程序进行盗取的机制通常被称为反射式 XSS。
正如例子中所显示的,XSS 漏洞是由于 HTTP 响应中包含了未验证的数据代码而引起的。受害者遭受 XSS 攻击的途径有三种:
- 系统从 HTTP 请求中直接读取数据,并在 HTTP 响应中返回数据。当攻击者诱使用户为易受攻击的 Web 应用程序提供危险内容,而这些危险内容随后会反馈给用户并在 Web 浏览器中执行时,就会发生 Reflected XSS 漏洞利用。发送恶意内容最常用的方法是,将恶意内容作为一个参数包含在公开发布或通过电子邮件直接发送给受害者的 URL 中。以这种手段构造的 URL 已成为多种网络钓鱼阴谋的核心,攻击者会借此诱骗受害者访问指向易受攻击站点的 URL。该站点将攻击者的内容反馈给受害者后,便会执行这些内容,接下来会将用户计算机中的各种私密信息(比如可能包含会话信息的 Cookie)传输给攻击者,或者执行其他恶意活动。
--- 应用程序将危险数据存储在数据库或其他可信赖的数据存储器中。这些危险数据随后会被回写到应用程序中,并包含在动态内容中。Persistent XSS 盗取发生在如下情况:攻击者将危险内容注入到数据存储器中,且该存储器之后会被读取并包含在动态内容中。从攻击者的角度看,注入恶意内容的最佳位置莫过于一个面向许多用户,尤其是相关用户显示的区域。相关用户通常在应用程序中具备较高的特权,或相互之间交换敏感数据,这些数据对攻击者来说有利用价值。如果某一个用户执行了恶意内容,攻击者就有可能以该用户的名义执行某些需要特权的操作,或者获得该用户个人所有的敏感数据的访问权限。
--- 应用程序之外的数据源将危险数据储存在一个数据库或其他数据存储器中,随后这些危险数据被当作可信赖的数据回写到应用程序中,并储存在动态内容中。
Recommendations
防止 XSS 漏洞的解决方法是确保在适当位置进行验证,并设置相关属性以防止漏洞。 由于 XSS 漏洞在应用程序的输出中包含恶意数据时出现,因此,合乎逻辑的方法是在数据流出应用程序的前一刻对其进行验证。
然而,由于 Web 应用程序常常会包含复杂而难以理解的代码,用以生成动态内容,因此,这一方法容易产生遗漏错误(遗漏验证)。降低这一风险的有效途径是对 XSS 也执行输入验证。 由于 Web 应用程序必须验证所有输入信息以避免其他漏洞(如 SQL Injection),因此,一种相对简单的解决方法是,加强应用程序的现有输入验证机制,将 XSS 检测包括其中。尽管有一定的价值,但 XSS 输入验证并不能取代严格的输出验证。应用程序可能通过共享的数据存储器或其他可信赖的数据源接受输入,而该数据存储器所接受的输入源可能并未执行适当的输入验证。因此,应用程序不能间接地依赖于该数据或其他任意数据的安全性。这就意味着,避免 XSS 漏洞的最佳方法是验证所有进入应用程序以及由应用程序传送至用户端的数据。
针对 XSS 漏洞进行验证最安全的方式是,创建一份安全字符允许列表,允许其中的字符出现在 HTTP 内容中,并且只接受完全由这些经认可的字符组成的输入。例如,有效的用户名可能仅包含字母数字字符,电话号码可能仅包含 0-9 的数字。然而,这种解决方法在 Web 应用程序中通常是行不通的,因为许多字符对浏览器来说都具有特殊的含义,编码时这些字符必须被视为合法输入,例如,一个 Web 设计电子公告栏就必须接受其用户提供的 HTML 片段。
更灵活的方法是采用拒绝列表,但其安全性较差,这种方法在使用输入之前就有选择地拒绝或转义了潜在的危险字符。为了创建这样一个列表,首先需要了解对于 Web 浏览器具有特殊含义的字符集。虽然 HTML 标准定义了哪些字符具有特殊含义,许多 Web 浏览器仍会设法更正 HTML 中的常见错误,并可能在特定的上下文中认为其他字符具有特殊含义。这就是为何我们不鼓励使用拒绝列表作为阻止 XSS 的方法。
卡耐基梅隆大学 (Carnegie Mellon University) 软件工程学院 (Software Engineering Institute) 下属的 CERT(R) (CERT(R) Coordination Center) 合作中心提供了有关各种上下文中认定的特殊字符的具体信息 : 在有关块级别元素的内容中(位于一段文本的中间): - "<" 是一个特殊字符,因为它可以引入一个标签。 - "&" 是一个特殊字符,因为它可以引入一个字符实体。 - ">" 是一个特殊字符,之所以某些浏览器将其认定为特殊字符,是基于一种假设,即该页的作者本想在前面添加一个 "<",却错误地将其遗漏了。
下面的这些原则适用于属性值:
- 对于外加双引号的属性值,双引号是特殊字符,因为它们标记了该属性值的结束。
- 对于外加单引号的属性值,单引号是特殊字符,因为它们标记了该属性值的结束。
- 对于不带任何引号的属性值,空格字符(如空格符和制表符)是特殊字符。
- "&" 与某些特定变量一起使用时是特殊字符,因为它引入了一个字符实体。
例如,在 URL 中,搜索引擎可能会在结果页面内提供一个链接,用户可以点击该链接来重新运行搜索。可以将这一方法运用于编写 URL 中的搜索查询语句,这将引入更多特殊字符:
- 空格符、制表符和换行符是特殊字符,因为它们标记了 URL 的结束。 - "&" 是特殊字符,因为它可引入一个字符实体或分隔 CGI 参数。
-
非 ASCII 字符(即 ISO-8859-1 编码表中所有大于 127 的字符)不允许出现在 URL 中,因此这些字符在此环境下被视为特殊字符。
-
在服务器端对在 HTTP 转义序列中编码的参数进行解码时,必须过滤掉输入中的 "%" 符号。
例如,当输入中出现"%68%65%6C%6C%6F"时,只有从输入的内容中过滤掉"%",上述字符串才能在网页上显示为"hello"。 在 <SCRIPT> </SCRIPT> 的正文内:
- 如果可以将文本直接插入到已有的脚本标签中,则必须过滤掉分号、圆括号、花括号和换行符。 服务器端脚本:
- 如果服务器端脚本会将输入中的感叹号 (!) 转换成输出中的双引号 ("),则可能需要对此进行更多过滤。 其他可能出现的情况:
- 如果攻击者以 UTF-7 格式提交了请求,则特殊字符"<"可能会显示为"+ADw-",并可能会绕过过滤。如果输出包含在没有确切指定编码格式的网页中,某些浏览器就会设法根据内容自动识别编码(此处采用 UTF-7 格式)。
在应用程序中确定针对 XSS 攻击执行验证的正确要点,以及验证过程中要考虑的特殊字符之后,下一个难点就是确定验证过程中处理各种特殊字符的方式。如果应用程序认定某些特殊字符为无效输入,那么您可以拒绝任何带有这些无效特殊字符的输入。第二种选择就是采用过滤手段来删除这些特殊字符。然而,过滤的负面作用在于,过滤内容的显示将发生改变。在需要完整显示输入内容的情况下,过滤的这种负面作用可能是无法接受的。 如果必须接受带有特殊字符的输入,并将其准确地显示出来,验证机制一定要对所有特殊字符进行编码,以便删除其具有的含义。
官方的 HTML 规范提供了特殊字符对应的 ISO 8859-1 编码值的完整列表。 许多应用程序服务器都试图避免应用程序出现 Cross-Site Scripting 漏洞,具体做法是为负责设置特定 HTTP 响应内容的函数提供各种实现方式,以检验是否存在进行 Cross-Site Scripting 攻击必需的字符。不要依赖运行应用程序的服务器,以此确保该应用程序的安全。对于任何已开发的应用程序,并不能保证在其生命周期中它会在哪些应用程序服务器中运行。由于标准和已知盗取方式的演变,我们不能保证应用程序服务器将继续保持同步。