[翻译]DOM-XSS/第三种XSS——看一下被忽视的XSS

原文地址:http://www.webappsec.org/projects/articles/071105.html

摘要

我们都知道XSS,对吧?一个漏洞,一个人发送恶意数据(通常是带有JS的HTML),然后恶意数据被应用程序在HTML上下文中输出回显,最终JS代码被执行。但是,错了。这里有一种不匹配这种描述的XSS,至少,在一些基本属性上不匹配。上面描述的XSS通常被称为“非持久XSS”/“反射XSS”(被嵌入在页面的恶意数据在下一个响应包中直接被返回给浏览器),或是被称为“持久XSS”/“存储型XSS”(在这种情况下,恶意数据在之后的某个时候被返回)。但是这里有第三种XSS攻击——首先这种XSS不依赖于向服务器发送恶意数据。这看起来似乎和定义或常识矛盾,但事实上,这种攻击有两个很好描述的例子。该技术主要讨论被称为“基于DOM的XSS”的第三种XSS。并没有声称攻击本身具有新颖性,这篇文章中的创新是注意到它们有着不同的“风味”,而且这种“风味”很有趣也很重要。

应用开发者和所有者需要理解基于DOM的XSS,因为它对Web应用程序构成了与标准XSS不同的威胁。因此,有许多在互联网上的Web应用程序都易受基于DOM的XSS的攻击,但在标准的XSS测试中被认为没有漏洞。开发者和站点运维任意(以及审计人员)需要熟悉检测基于DOM的XSS漏洞,以及防御他们的技术——两者都不同于适用在标准XSS中的技术。

介绍

假定读者具有XSS的基本知识([1],[2],[3],[4],[8])。根据[4]中的定义,XSS通常被分为“非持久型”和“持久型”,或是“反射型”和“存储型”。“非持久型”意味着恶意的JS payload在受害者进行HTTP请求后被服务器直接输出到响应中。“持久型”意味着payload被系统存储,然后被有漏洞的系统嵌入在一个HTML网页中提供给受害者。正如在摘要中提到的,这种分类方式假定:XSS的基本属性是恶意payload从浏览器端到服务器端,然后返回到相同(非持久型)或不同(持久型)的浏览器端。这篇文章指出这是一种误解。虽然没有很多在野反例,但它只是存在XSS攻击,但是它不依赖于在服务器的某些响应页面嵌入payload,这是非常重要的,因为它对检测和保护方法有重要影响。这就是本文中讨论的内容。

例子和讨论

在描述基本情景之前,强调一下这里描述的技术在之前已经公开([5],[6][7])。因此,并未声称以下是新技术(即使有一些是绕过技术)。

受该漏洞影响的HTML页面需要有的一个前提是:以不安全的方式来使用来自document.locationdocument.URLdocument.referrer(或其他任何受攻击者影响的对象)的数据。

注意:对于不熟悉JS对象的读者:当JS在浏览器中被执行时,浏览器会给JS代码提供几个代表DOM(Document Object Model)的对象。document对象是这些对象中最主要的,它代表了浏览器所实现的大多数页面属性。document对象包含了许多子对象,例如location,URLreferrer在浏览器看来,这些内容需要它来填充(这很重要,我们稍后会看到具体例子)。所以,浏览器将以它理解的形式,将document.URLdocument.location填充为页面的URL。注意,这些对象不是从HTML正文中提取得到的——它们不会出现在页面的数据中。document对象包含一个body对象,body对象表现为是一个已经经过解析的HTML。找到一个包含能够解析URL的JS代码的页面(通过document.URL或是document.location),是很容易的。以下是这种逻辑的一个例子。

与[2]中的示例类似(并且与[7]中的示例基本相同),例如,考虑下面的HTML页面(假设这是http://www.vulnerable.site/welcome.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<HTML>
<TITLE>Welcome!</TITLE>
Hi
<SCRIPT>
var pos=document.URL.indexOf("name=")+5;
document.write(document.URL.substring(pos,document.URL.length));
</SCRIPT>
<BR>
Welcome to our system
</HTML>

正常情况下,此HTML页面将用于欢迎用户,如:

1
http://www.vulnerable.site/welcome.html?name=Joe

然而,一个这样的请求:

1
2
http://www.vulnerable.site/welcome.html?name=
<script>alert(document.cookie)</script>

将会导致XSS。让我们看看这是为什么?受害者的浏览器收到这个链接,将会向www.vulnerable.site发送一个HTTP请求,然后会收到上述HTML页面(静态的!)。然后,受害者的浏览器开始把这个HTML解析为DOM。这个DOM包含了一个叫做document的对象,这个对象包含了一个叫做URL的属性,并且,作为DOM创建时的一部分,这个属性被当前页面的URL填充。当解析器收到了那个JS代码,解析器就会将JS代码执行,然后修改了页面原始的HTML。在这个案例中,代码引用了document.URL,并且,这个字符串的一部分在解析时被嵌入到了HTML中,然后,该部分在相同页面的上下文中被直接解析并执行了,产生了XSS。

注意:

  1. 恶意的payload从始至终没有插入到原始的HTML页面中(这与其他XSS不同)
  2. 这个exp仅在浏览器不修改URL中的字符时有效。当没有直接在地址栏中键入时,Mozilla会自动编码在document.URL中的<>(分别为$3c%3E),因此,Mozilla不会像示例中的这么易受攻击。如果不需要原始形式的<>,则易受攻击。IE6.0不会编码<>,因此它会像示例中的一样易受攻击。

当然,直接嵌入HTML只是一个攻击点,有各种各样的场景不需要<>,因此,Mozilla也不能免受攻击。

规避标准检测和预防技术

在上述的例子中,可能仍有争议,payload确实到达了服务器(在HTTP请求的查询参数中),所以它可以像其他XSS攻击一样被检测到。但是,即使这样,也可以通过下面的方式利用。考虑一下下面的payload:

1
2
http://www.vulnerable.site/welcome.html#name=
<script>alert(document.cookie)<script>

注意文件名之后的#符号,它告诉浏览器,它后面的一切都是锚点,不是查询参数的一部分。IE6.0和Mozilla不会发送锚点到服务器,因此,服务器看到的内容就是http://www.vulnerable.site/welcome.html,payload不会被服务器看到。因此,我们可以看到,这种绕过技术是由于浏览器不将恶意payload发送到服务器所导致的。

有时,完全隐藏payload是不可能的:在[5]和[6]中,恶意payload是用户名的一部分,在一个URL中,看起来像是这样:http://username@host/。在某些情况下,浏览器发送一个含有认证头的请求,认证头中包含用户名,用户名中含有恶意payload,因此,payload会到达服务器(通常经过Base64编码——所以IDS/IPS首先需要将数据解码,然后检测攻击)。但是,服务器不需要嵌入此payload来XSS攻击。

显然,在payload可以被完全隐藏的情况下,在线入侵检测(IDS)与防御系统(IPS,WAF)产品不能完全阻止这种攻击,假设确实可以从已知位置调用易受攻击的脚本。即使必须将payload发送到服务器,在许多情况下也可以通过下面的方式,避免被检测到,例如,特定的参数被保护(如上例中的name参数),则攻击轻微的变化也会使XSS攻击成功:

1
2
http://www.vulnerable.site/welcome.html?notname=
<script>(document.cookie)</script>

一个更严格的安全策略是要求发送name参数(以避免上述使用的包含名称和数字的数字的技巧)。那么,我们可以发送这个:

1
2
http://www.vulnerable.site/welcome.html?notname=
<script>alert(document.cookie)<script>&name=Joe

如果策略限制其他参数名称(例如,foobar),则以下变体将能成功:

1
2
http://www.vulnerable.site/welcome.html?foobar=
name=<script>alert(document.cookie)<script>&name=Joe

注意,被忽略的参数(foobar)必须首先出现,并且它的值包含payload。

从攻击者的角度来看,[7]中的场景甚至更好,因为完整的document.location被写入HTML页面(Javascript代码不扫描特定的参数名称)。因此,攻击者可以完全隐藏payload,例如,发送:

1
2
/attachment.cgi?id=&action=
foobar#<script>alert(document.cookie)</script>

即使payload被服务器检测,保护也只能在请求被完全拒绝、或者用一些报错文本替换时。再考虑一下[5]和[6],如果认证头被中间保护系统简单地移除了,那么只要返回原来的页面,这个保护就没有效果了。同样的,任何在服务器上对数据进行清洗,或者是通过移除有问题的字符,或者是对有问题的字符进行编码的尝试,在抵御这种攻击时都是无效的。

在使用document.referer的情况下,payload通过Referer头被发送到服务器。然而,如果用户的浏览器,或中间代理删除掉Referer头,那么就会没有任何攻击痕迹——这就可能被完全忽视。

概括地说,传统方法:

  1. 在服务器端,对输出数据进行HTML编码
  2. 在服务器端,移除/编码有问题的输入字符

这些在抵御基于DOM的XSS上都不有效。

通过对输入点进行注入,这种形式进行的自动化漏洞挖掘(有时被称为模糊测试)可能是无效的,因为使用此技术的产品通常根据注入的数据是否存在于响应页面中来评估结果(而不是在浏览器上下文中执行客户端代码并观察运行时效果)。然而,如果一个产品能够静态分析在页面中发现的JS代码,则可能会指示出可疑的地方(见下文)。当然,如果这个产品能够执行JS(并且能正确填充DOM对象),或模拟执行过程,则有可能检测到这种攻击。

使用浏览器进行手动漏洞挖掘会有用,因为浏览器会执行客户端JS代码。当然,一个漏洞挖掘产品可能会采用这种技术,执行客户端代码来检测运行时效果。

有效防御

  1. 避免在客户端使用客户端数据来对文档进行重写、重定向或者其他敏感操作。这些效果中的大部分都可以通过在服务器端使用动态页面来实现。
  2. 分析并强化客户端JS代码。应检查可能受用户(攻击者)影响的DOM对象的引用,包括(但不限于):

    • document.URL
    • document.URLUnencoded
    • document.location(和它的许多属性)
    • document.referrer
    • window.location(和它的许多属性)

      注意,document对象的属性或window对象的属性可以通过多种语法方式引用(如window.location),隐式的(如location),或是通过获取窗口的句柄并使用它(如handle_to_some_window.location)。

      无论是显式的还是隐式的,只要是通过对原始HTML或DOM对象本身进行访问,修改DOM的场景都应加以特别关注,例如(绝不是详尽的列表,还可能有各种浏览器扩展):

    • 向原始HTML写入,如:

      • document.write(...)
      • document.writeln(...)
      • document.body.innerHtml=...
    • 直接修改DOM(包括DHTML事件),如:
      • document.forms[0].action=...(和其他各种形式)
      • document.attachEvent(…)
      • document.create…(…)
      • document.execCommand(…)
      • document.body. ...(通过body对象访问DOM)
      • window.attachEvent(...)
    • 替换document的URL,如:
      • document.location=...(和根据此分配的location的href、主机和主机名)
      • document.location.hostname=...
      • document.location.replace(...)
      • document.location.assign(...)
      • document.URL=...
      • document.navigate(...)
    • 打开或修改window,如:
      • document.open(...)
      • window.open(...)
      • window.location.href=...和根据此分配的location的href、主机和主机名)
    • 直接执行脚本,如:

      • eval(...)
      • window.execScript(...)
      • window.setInterval(...)
      • window.setTimeout(...)

      继续上面的例子,一个有效的防御能够使用下面的代码替换部分原始脚本,它验证写入HTML页面的字符串是否仅包含字母数字字符:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <SCRIPT>
      var pos=document.URL.indexOf("name=")+5;
      var name=document.URL.substring(pos,document.URL.length);
      if (name.match(/^[a-zA-Z0-9]*$/)) {
      document.write(name);
      } else {
      window.alert("Security error");
      }
      </SCRIPT>
这种功能能够(并且应该)作为一个用于处理数据的通用库被提供(即,一组执行输入验证和/或过滤的JS函数)。缺点是安全逻辑被暴露给攻击者——因为它被嵌入到了HTML代码中。这会使它非常容易被理解,然后被攻击。当在上述的情景中,情况非常容易,但在大多数更复杂的、安全检查不完美的情况下,这会起作用。
  1. 采用非常严格的IPS侧链,例如,页面welcome.html只接收一个名为name的参数,其内容经过检查,任何不合法(包括过多参数或没有参数)都将会导致不返回原始HTML页面,同样,如果有任何其他不合法行为(像是认证头或是包含有问题数据的Referer头),原始HTML页面也不会返回。不过,在某些情况下,即使这样也无法保证攻击能够被抵御。

关于重定向漏洞的说明

上面的讨论一直基于XSS,但在许多情况下,仅使用客户端脚本(不安全地)将浏览器重定向到另一个位置本身就被认为是漏洞。在这种情况下,上述技术和检测方案仍然适用。

结论

虽然公开描述的大多数XSS攻击确实依赖于服务器将用户数据物理地嵌入到响应HTML页面中,但是存在不依赖于服务器端嵌入数据的XSS攻击。在讨论检测和预防XSS的方法时,这具有重要意义。到目前为止,公开讨论的几乎所有检测和预防技术都假设XSS攻击是服务器接收恶意用户输入并将其嵌入HTML页面。由于此假设不适用于本文所述的XSS攻击(或仅部分保留),因此许多技术无法检测并防止此类攻击。

依赖于服务器端将用户数据嵌入到网站的XSS攻击被分类为“非持久型”(或“反射型”)和“持久性型”(或“存储型”)。因此建议将第三种XSS(不依赖于服务器端嵌入的那种)命名为“基于DOM的XSS”。

Here is a comparison between standard XSS and DOM Based XSS:

标准XSS 基于DOM的XSS
根源 在输出的HTML页面中不安全地嵌入客户端输入 不安全的引用和使用(在客户端代码中)DOM对象,这些对象不完全由服务器提供的页面控制
Owner Web开发人员(CGI) Web开发人员(HTML)
页面性质 动态的(CGI) 通常是静态的(HTML),但不一定
漏洞检测 - 手工注入检测
- 自动注入检测
- 代码审计(需要能够获取到页面源代码的权限)
- 手工注入检测
- 代码审查(可以远程完成!)
攻击发现 - Web服务器日志
- 在线攻击检测工具(IDS,IPS,WAF)
如果绕过技术适用并被使用,则无法进行服务器端检测
有效防御 - 服务器端数据验证
- 攻击防护工具(IPS,应用防火墙)
- 使用JS在客户端进行数据验证
- 可选的服务器端逻辑验证

参考

注意:以下网址在撰写本文时(2005年7月4日)是最新的。其中一些材料是实时文档,因此可能会更新以反映本文的见解。

[1] “CERT Advisory CA-2000-02 - Malicious HTML Tags Embedded in Client Web Requests”, CERT, February 2nd, 2000
http://www.cert.org/advisories/CA-2000-02.html

[2] “Cross Site Scripting Explained”, Amit Klein, June 2002
http://crypto.stanford.edu/cs155/CSS.pdf

[3] “Cross-Site Scripting”, Web Application Security Consortium, February 23rd, 2004
http://www.webappsec.org/projects/threat/classes/cross-site_scripting.shtml

[4] “Cross Site Scripting (XSS) Flaws”, The OWASP Foundation, updated 2004 http://www.owasp.org/documentation/topten/a4.html

[5] “Thor Larholm security advisory TL#001 (IIS allows universal CrossSiteScripting)”, Thor Larholm, April 10th, 2002
http://www.cgisecurity.com/archive/webservers/iis_xss_4_5_and_5.1.txt

(see also Microsoft Security Bulletin MS02-018 http://www.microsoft.com/technet/security/bulletin/MS02-018.mspx)

[6] “ISA Server Error Page Cross Site Scripting”, Brett Moore, July 16th, 2003 http://www.security-assessment.com/Advisories/ISA%20XSS%20Advisory.pdf
(see also Microsoft Security Bulletin MS03-028 http://www.microsoft.com/technet/security/bulletin/MS03-028.mspx and a more elaborate description in “Microsoft ISA Server HTTP error handler XSS”, Thor Larholm, July 16th, 2003 http://www.securityfocus.com/archive/1/329273)

[7] “Bugzilla Bug 272620 - XSS vulnerability in internal error messages”, reported by Michael Krax, December 23rd, 2004
https://bugzilla.mozilla.org/show_bug.cgi?id=272620

[8] “The Cross Site Scripting FAQ”, Robert Auger, May 2002 (revised August 2003)
http://www.cgisecurity.com/articles/xss-faq.shtml

关于作者

Amit Klein是一位著名的Web应用程序安全研究员。 Klein先生撰写了许多关于各种Web应用技术的研究论文 —— 从HTTP到XML,SOAP和Web服务 —— 并涵盖了许多主题 —— HTTP request smuggling(HTTP请求走私), insecure indexing, blind XPath injection(盲打XPath注入), HTTP response splitting(HTTP响应拆分),.NET Web应用程序的安全,跨站点脚本,cookie中毒等。 他的作品已发表在Dobb博士的期刊,SC杂志,ISSA期刊和IT审计期刊上; 已在SANS和CERT会议上发表; 并在许多学术教学大纲中使用和引用。

Klein先生是WASC(Web应用安全联盟)成员。

窝很可爱,请给窝钱