攻击其他用户

绝大多数针对Web应用程序的攻击主要以服务器端应用程序为攻击目标。当然,许多这类攻击会侵害到其他用户,例如,盗窃其他用户数据的SQL注入攻击。但是,攻击者所使用的基本攻击方法是以无法预料的方式与服务器进行交互的,目的是执行未授权操作并非法访问数据。

本章描述的攻击属于另外一种类型,因为攻击者的主要对象是应用程序的其他用户。服务器端应用程序仍然存在所有相关漏洞;然而,攻击者利用应用程序的一些行为执行针对其他终端用户的恶意操作。这些操作可能会造成一些与前面分析过的攻击相同的后果,如会话劫持、未授权操作和披露个人信息;还可能导致其他恶果,如记录键击或在用户的计算机上执行任意命令。

近年来,软件安全其他领域的关注焦点已逐渐由服务器端攻击转变为客户端攻击。举例来说,Microsoft过去会定期宣布其服务器产品中存在的严重安全漏洞。虽然他们也披露大量客户端缺陷,但这类缺陷很少受到关注;因为对攻击者而言,服务器是一个更具吸引力的目标。仅仅几年内,这种情况就发生显著改变。自Microsoft的IIS 6 Web服务器首次发布以来,人们已经在Microsoft Internet Explorer浏览器中发现了大量漏洞。随着人们对安全威胁意识的普遍增强,软件开发者与黑客之间的前沿战场已经由服务器转向客户端。

虽然Web应用程序安全状况尚未发生上述巨大的转变,但也出现了相同的趋势。20世纪90年代末,因特网上的大多数应用程序中充斥着命令注入之类的严重缺陷,任何攻击者只要具备一点点相关知识,就能够轻易发现并利用这些弱点。尽管许多这种类型的漏洞今天依然存在,但数量逐渐减小并且变得更加难以利用。然而,即使是最为注重安全的应用程序,也仍然包含许多可轻易发现的客户端缺陷。此外,应用程序的服务器端以有限、可控的方式运行,而客户端可使用任意数量的各种浏览器技术(包括各种版本),由此客户端面临大范围可成功实施的攻击向量。

许多年前,各种服务器端漏洞大行其道;如今,随着人们首次谈及会话固定之类的漏洞,客户端漏洞开始成为最近研究的主要焦点。客户端攻击成为以Web安全为报导对象的新闻媒体的主要关注焦点,间谍软件、钓鱼攻击和木马等名词成为许多以前从未听说过SQL注入或路径遍历的新闻记者的口头禅。针对Web应用程序用户的攻击也日益成为有利可图的犯罪行为。如果一家因特网银行拥有1000万个用户,并且不需要熟练的技能,只需要通过相对简单的攻击方法就可以攻破其中1%的用户,那何必还要费神去入侵这家银行呢?

针对其他应用程序用户的攻击形式各异,它们之间的微妙之处与细微差别常常被人们忽略。通常,与主要的服务器端攻击相比,人们对这些攻击也知之甚少,即使经验丰富的渗透测试员也会混淆或忽略各种不同的漏洞。本章将描述各种常见的漏洞,并说明渗透测试员在确认并利用这些漏洞时所需采取的实用步骤。

本章主要介绍跨站点脚本(XSS)。这类漏洞导致了针对其他用户的重量级攻击。从某种程度上说,XSS是在Web应用程序中发现的最为普遍的漏洞,困扰着现在绝大多数的应用程序,包括因特网上一些最为注重安全的应用程序,如电子银行使用的应用程序。在下一章中,我们将介绍各种针对用户的其他类型的攻击,其中的一些攻击与XSS非常类似。

 错误观点 “用户之所以被攻破,是因为他们没有安全意识。”

从某种程度上说,这种观点是正确的,但是,尽管用户采取了安全防御,一些针对应用程序用户的攻击仍然能够取得成功。保存型XSS攻击能够攻破最具安全意识的用户,而无须与用户进行任何交互。在第13章,我们将介绍许多其他方法,可在用户不知情的情况下攻破具有安全意识的用户。

最初,当XSS在Web应用程序安全社区广为人们所知时,一些专业渗透测试人员倾向于将XSS当做一种“次要”漏洞。这一部分是因为该漏洞在Web应用程序中极为常见,也因为与服务器端命令注入等许多漏洞相比,XSS并不能被独立黑客直接用于攻击应用程序。随着时间的推移,这种观点已发生改变,如今,XSS已被人们视为Web应用程序面临的最主要的安全威胁。随着对客户端攻击的研究不断深入,人们开始讨论各种其他复杂性与利用XSS漏洞的攻击不相上下的攻击。与此同时,现实世界中也出现了大量利用XSS漏洞攻破知名机构的攻击。

通常情况下,XSS是一类主要的应用程序安全缺陷,它常常与其他漏洞一起造成破坏性的后果。有时,XSS攻击也可能转变成某种病毒或能够自我繁殖的蠕虫,这种攻击确实非常严重。

 错误观点 “不可能通过XSS控制一个Web应用程序。”

我们曾仅使用XSS攻击控制大量的应用程序。在适当的情况下,技术熟练的攻击者利用XSS漏洞即可完全攻破一个应用程序。下面将说明攻击者如何实施攻击。

XSS的分类

XSS漏洞表现为各种形式,并且可分为3种类型:反射型、保存型和基于DOM的XSS漏洞。虽然这些漏洞具有一些相同的特点,但在如何确定及利用这些漏洞方面,仍然存在一些重要的差异。下面我们将分别介绍每一类XSS漏洞。

反射型XSS漏洞

如果一个应用程序使用动态页面向用户显示错误消息,就会造成一种常见的XSS漏洞。通常,该页面会使用一个包含消息文本的参数,并在响应中将这个文本返回给用户。对于开发者而言,使用这种机制非常方便,因为它允许他们从应用程序中调用一个定制的错误页面,而不需要对错误页面中的消息分别进行硬编码。

例如,下面的URL返回如图12-1所示的错误消息:

图12-1 一条动态生成的错误消息

分析返回页面的HTML源代码后,我们发现,应用程序只是简单复制URL中message参数的值,并将这个值插入到位于适当位置的错误页面模板中:

提取用户提交的输入并将其插入到服务器响应的HTML代码中,这是XSS漏洞的一个明显特征;如果应用程序没有实施任何过滤或净化措施,那么它很容易受到攻击。让我们来看看如何实施攻击。

下面的URL经过专门设计,它用一段生成弹出对话框的JavaScript代码代替错误消息:

请求这个URL将会生成一个HTML页面,其中包含以下替代原始消息的脚本:

可以肯定,如果该页面在用户的浏览器中显示,弹出消息就会出现,如图12-2所示。

图12-2 一次概念验证XSS攻击

进行这个简单的测试有助于澄清两个重要问题:首先,message参数的内容可用任何返回给浏览器的数据替代;其次,无论服务器端应用程序如何处理这些数据(如果有),都无法阻止提交JavaScript代码,一旦错误页面在浏览器中显示,这些代码就会执行。

尝试访问

http://mdsec.net/error/5/

 注解 如果在Internet Explorer中尝试这样的示例,弹出窗口可能无法显示,浏览器可能会显示以下消息:“为帮助阻止跨站点脚本,Internet Explorer已修改此页面。”这是因为最新版本的Internet Explorer包含一个旨在帮助用户防范反射型XSS漏洞的内置机制。如果要测试这些示例,可以尝试使用其他未使用这种保护机制的浏览器,或者通过以下方式禁用XSS筛选器:“工具” → “Internet选项” → “安全” → “自定义级别”,在“启用XSS筛选器”下选择“禁用”。我们将在本章后面部分讨论XSS筛选器的工作机制,以及避开这种筛选器的方法。

在现实世界的Web应用程序中存在的XSS漏洞,有近75%的漏洞属于这种简单的XSS bug。由于利用这种漏洞需要设计一个包含嵌入式JavaScript代码的请求,随后这些代码又被反射到任何提出请求的用户,因而它被称作反射型XSS。攻击有效载荷分别通过一个单独的请求与响应进行传送和执行。为此,有时它也被称为一阶XSS。

利用漏洞

下文将会介绍,利用XSS漏洞攻击应用程序其他用户的方式有很多种。最简单的一种攻击,也是我们常用于说明XSS漏洞潜在影响的一种攻击,可导致攻击者截获通过验证的用户的会话令牌。劫持用户的会话后,攻击者就可以访问该用户经授权访问的所有数据和功能(参见第7章)。实施这种攻击的步骤如图12-3所示。

图12-3 反射型XSS攻击的实施步骤

(1)用户正常登录应用程序,得到一个包含会话令牌的cookie:

(2)攻击者通过某种方法(详情见下文)向用户提交以下URL:

和前面生成一个对话框消息的示例一样,这个URL包含嵌入式JavaScript代码。但是,这个示例中的攻击有效载荷更加恶毒。

(3)用户从应用程序中请求攻击者传送给他们的URL。

(4)服务器响应用户的请求。由于应用程序中存在XSS漏洞,响应中包含攻击者创建的JavaScript 代码。

(5)用户浏览器收到攻击者的JavaScript代码,像执行从应用程序收到的其他代码一样,浏览器执行这段代码。

(6)攻击者创建的恶意JavaScript代码为:

这段代码可让用户浏览器向mdattacker.net (攻击者拥有的一个域)提出一个请求。请求中包含用户访问应用程序的当前会话令牌:

(7)攻击者监控访问mdattacker.net的请求并收到用户的请求。攻击者使用截获的令牌劫持用户的会话,从而访问该用户的个人信息,并“代表”该用户执行任意操作。

 注解 第6章已经介绍过,一些应用程序保存一个持久性cookie,以在用户每次访问时重新对其进行有效验证,例如,执行“记住我”功能。这时,就没有必要执行上述过程中的第一个步骤。即使目标用户并未处于活动状态或登录应用程序,攻击者仍然能够成功实现目标。为此,以这种方式使用cookie的应用程序更易受到XSS漏洞的影响。

完成上述步骤后,读者可能会心存疑惑:如果攻击者能够诱使用户访问他选择的URL,那么他为什么还要费这么大力气通过应用程序中的XSS漏洞传送自己的恶意JavaScript代码呢?为什么他不在mdattacker.net上保存一段恶意脚本,并向用户传送一个直接指向这段脚本的链接呢?这段脚本不是可以和上例中的脚本一样执行吗?

要了解攻击者为什么需要利用XSS漏洞,需要回顾第3章介绍的同源策略。为防止不同域在用户浏览器中彼此干扰,浏览器对从不同来源(域)收到的内容进行隔离。攻击者的目的不是单纯地执行任意脚本,而是截获用户的会话令牌。浏览器不允许任何旧有脚本访问一个站点的cookie,否则,会话就很容易被劫持。而且,只有发布cookie的站点能够访问这些cookie:仅在返回发布站点的HTTP请求中提交cookie ;只有通过该站点返回的页面所包含或加载的JavaScript才能访问cookie。因此,如果mdattacker.net上的一段脚本查询document.cookie,它将无法获得mdsec.net发布的cookie,劫持攻击也不会成功。

就用户的浏览器而言,利用XSS漏洞的攻击之所以取得成功,是因为攻击者的恶意JavaScript是由mdsec.net送交给它的。当用户请求攻击者的URL时,浏览器向http://mdsec.net/error/5/Error.ashx提交一个请求,然后应用程序返回一个包含一段JavaScript的页面。和从mdsec.net收到的任何JavaScript一样,浏览器执行这段脚本,因为用户信任mdsec.net。这也就是为何攻击的脚本能够访问mdsec.net发布的cookie的原因,虽然它实际来自其他地方。这也是为何该漏洞被称作跨站点脚本的原因。

保存型XSS漏洞

另一种常见的XSS漏洞叫做保存型跨站点脚本。如果一名用户提交的数据被保存在应用程序中(通常保存在一个后端数据库中),然后不经适当过滤或净化就显示给其他用户,此时就会出现这种漏洞。

在支持终端用户交互的应用程序中,或者在具有管理权限的员工访问同一个应用程序中的用户记录和数据的应用程序中,保存型XSS漏洞很常见。例如,以一个拍卖应用程序为例,它允许买家提出与某件商品有关的问题,然后由卖家回答。如果一名用户能够提出一个包含嵌入式JavaScript的问题,而且应用程序并不过滤或净化这个JavaScript,那么攻击者就可以提出一个专门设计的问题,在任何查看该问题的用户(包括卖家和潜在的买家)的浏览器中执行任意脚本。在这种情况下,攻击者就可让不知情的用户去竞标一件他不想要的商品;或者让一位卖家接受他提出的低价,结束竞标。

一般情况下,利用保存型XSS漏洞的攻击至少需要向应用程序提出两个请求。攻击者在第一个请求中传送一些专门设计的数据,其中包含恶意代码,应用程序接受并保存这些数据。在第二个请求中,一名受害者查看某个包含攻击者的数据的页面,这时恶意代码开始执行。为此,这种漏洞有时也叫做二阶跨站点脚本。(在这个示例中,使用XSS实际上并不准确,因为攻击中没有跨站点元素。但由于这个名称被人们广泛使用,因此我们在这里仍然沿用它。)

图12-4说明了一名攻击者如何利用保存型XSS漏洞,实施上述利用反射型XSS漏洞实施的相同会话劫持攻击。

图12-4 保存型XSS漏洞的实施步骤

尝试访问

本示例包含一项搜索功能,可用于显示当前用户输入的查询,以及其他用户最近输入的查询列表。由于查询将按原样显示,应用程序将易于受到反射型和保存型XSS攻击。看看是否能够找到这两种漏洞。

http://mdsec.net/search/11/

反射型与保存型XSS攻击在实施步骤上存在两个重要的区别,这也使得后者往往造成更大的安全威胁。

首先,在反射型XSS脚本攻击中,要利用一个漏洞,攻击者必须以某种方式诱使受害者访问他专门设计的URL。而保存型XSS脚本攻击则没有这种要求。在应用程序中展开攻击后,攻击者只需要等待受害者浏览已被攻破的页面或功能。通常,这个页面是一个正常用户将会主动访问的常规页面。

其次,如果受害者在遭受攻击时正在使用应用程序,攻击者就更容易实现其利用XSS漏洞的目的。例如,如果用户当前正在进行会话,那么攻击者就可以劫持这个会话。在反射型XSS攻击中,攻击者可能会说服用户登录,然后单击他们提供的一个链接,从而制造这种情况。或者他可能会部署一个永久性的有效载荷并等待用户登录。但是,在保存型XSS攻击中,攻击者能够保证,受害用户在他实施攻击时已经在访问应用程序。因为攻击有效载荷被保存在用户自主访问的一个应用程序页面中,所以,当有效载荷执行时,任何攻击受害者都在使用应用程序。而且,如果上述页面位于应用程序通过验证的区域内,那么那时攻击受害者一定已经登录。

反射型与保存型XSS攻击之间的这些区别意味着保存型XSS漏洞往往会给应用程序带来更严重的安全威胁。许多时候,攻击者可以向应用程序提交一些专门设计的数据,然后等待受害者访问它们。如果其中一名受害者是管理员,那么攻击者就能够完全攻破整个应用程序。

基于DOM的XSS漏洞

反射型和保存型XSS漏洞都表现出一种特殊的行为模式,其中应用程序提取用户控制的数据并以危险的方式将这些数据返回给用户。第三类XSS漏洞并不具有这种特点。在这种漏洞中,攻击者的JavaScript通过以下过程得以执行。

用户请求一个经过专门设计的URL,它由攻击者提交,且其中包含嵌入式JavaScript。

服务器的响应中并不以任何形式包含攻击者的脚本。

当用户的浏览器处理这个响应时,上述脚本得以处理。

这一系列事件如何发生呢?由于客户端JavaScript可以访问浏览器的文本对象模型(Document Object Model,DOM),因此它能够决定用于加载当前页面的URL。由应用程序发布的一段脚本可以从URL中提取数据,对这些数据进行处理,然后用它动态更新页面的内容。如果这样,应用程序就可能易于受到基于DOM的XSS攻击。

回到前面的反射型XSS漏洞中的示例,其中服务器端应用程序将一个URL参数值复制到一条错误消息中。另一种实现相同功能的办法是由应用程序每次返回相同的静态HTML,并使用客户端JavaScript动态生成消息内容。

例如,假设应用程序返回的错误页面包含以下脚本:

这段脚本解析URL,提取出message参数的值,并把这个值写入页面的HTML源代码中。如果按开发者预想的方式调用,它可以和前面的示例中一样,用于创建错误消息。但是,如果攻击者设计出一个URL,并以JavaScript代码作为message参数,那么这段代码将被动态写入页面中,并像服务器返回代码一样得以执行。在这个示例中,前面示例中利用反射型XSS漏洞的同一个URL也可用于生成一个对话框:

尝试访问

http://mdsec.net/error/18/

利用基于DOM的XSS漏洞的过程如图12-5所示。

图12-5 基于DOM的XSS攻击的实施步骤

与保存型XSS漏洞相比,基于DOM的XSS漏洞与反射型XSS漏洞有更大的相似性。利用它们通常需要攻击者诱使一名用户访问一个包含恶意代码的专门设计的URL,并由服务器响应那个确保得恶意代码得以执行的特殊请求。但是,在利用反射型与基于DOM的XSS漏洞的细节方面,还存在一些重要的差异,这点我们在稍后讨论。

进行中的XSS攻击

要了解XSS漏洞导致的严重后果,有必要分析一些真实的XSS攻击示例,这有助于我们了解XSS攻击能够执行的各种恶意操作,以及如何针对受害者实施这类攻击。

真实XSS攻击

2010年,Apache Foundation被攻击者利用其问题追踪应用程序中的漏洞、通过反射型XSS攻击攻破。首先,攻击者发布一个使用重定向服务进行模糊处理的链接,该链接指向一个利用上述XSS漏洞获取登录用户的会话令牌的URL。如果管理员单击该链接,他的会话将被攻破,攻击者将获得对应用程序的管理访问权限。然后,攻击者修改某个项目的设置,将该项目的上传文件夹更改为应用程序的Web根目录中的可执行目录。随后,攻击者向此文件夹上传一个木马登录表单,从而获取特权用户的用户名和密码。通过这种方法,攻击者确定一些在基础架构的其他系统中重复使用的密码,并完全攻破这些系统,将攻击范围扩展到易受攻击的Web应用程序之外。

有关此次攻击的详情,请参阅以下URL:

2005年,发现社交网络站点MySpace易于受到保存型XSS攻击。虽然MySpace的应用程序实施了过滤,防止用户在他们的用户资料页面嵌入JavaScript脚本,但是,一位名叫Samy的用户找到了一种避开这些过滤的方法,并在用户资料页面中插入了一些JavaScript脚本。如果一名用户查看他的用户资料,这段脚本就会执行,导致受害者的浏览器执行各种操作。这就造成了两个严重后果:首先,它把Samy加为受害者的“朋友”;其次,它把上述脚本复制到受害者自己的用户资料页面中,因此,任何查看受害者用户资料的用户也会成为这次攻击的受害者。结果,一个基于XSS的蠕虫在因特网上迅速扩散,几小时内,Samy收到了近100万个朋友邀请。为此,MySpace被迫关闭它的应用程序,从所有用户的资料中删除恶意脚本,并修复反XSS过滤机制中的缺陷。

有关此次攻击的详细信息,请参阅以下URL:

http://namb.la/popular/tech.html

当收件人查阅电子邮件时,邮件内容在浏览器中显示;Web邮件应用程序的这种行为本身就存在着保存型XSS攻击风险。电子邮件中可能包含HTML格式的内容,因此应用程序会立即将第三方HTML复制到向用户显示的页面中。2009年,一家名为StrongWebmail的Web邮件提供商悬赏1万美元,征询能够侵入其CEO电子邮件的黑客。最终,黑客在该Web邮件应用程序中发现一个保存型XSS漏洞,如果收件人查看恶意电子邮件,黑客就可以执行任意JavaScript。于是,黑客们向该CEO发送了一份恶意电子邮件,攻破了他在应用程序上的会话,从而赢得了赏金。

有关此次攻击的详细信息,请参阅以下URL:

http://blogs.zdnet.com/security/?p=3514

2009年,Twitter成为了两个XSS蠕虫的受害者,这两个蠕虫利用保存型XSS漏洞在Twitter用户间进行传播,并发布推文来宣传蠕虫作者的网站。人们还在Twitter中发现各种基于DOM的XSS漏洞,这主要是因为它在客户端大量使用类似于Ajax的代码所致。

有关这些漏洞的详细信息,请参阅以下URL:

www.cgisecurity.com/2009/04/two-xss-worms-slam-twitter.html

http://blog.mindedsecurity.com/2010/09/twitter-domxss-wrong-fix-andsomething.html

XSS攻击有效载荷

迄今为止,我们已经重点分析了典型的XSS攻击有效载荷,如截获一名受害者的会话令牌,劫持他的会话,进而“作为”受害者使用应用程序,执行任意操作并占有该用户的账户。实际上,还有其他大量的攻击有效载荷可通过任何类型的XSS漏洞传送。

虚拟置换

这种攻击需要在一个Web应用程序页面注入恶意数据,从而向应用程序用户传送误导性信息。它包括简单向站点中注入HTML标记,或者使用脚本(有时保存在外部服务器上)在站点中注入精心设计的内容和导航。这种攻击被称为虚拟置换(virtual defacement),因为攻击者实际上并没有修改保存在目标Web服务器上的内容,而是利用应用程序处理并显示用户提交的输入方面的缺陷实现置换。

除造成无关紧要的损害外,这种攻击也可用于严重的犯罪活动中。一个专门设计的置换,如果以可信的方式传送给适当的接受者,可能会被新闻媒体报导,对人们的行为、股票价格等造成重大影响,从而帮助攻击者从中获得经济利益,如图12-6所示。

图12-6 一次利用XSS漏洞的虚拟置换攻击

注入木马功能

这种攻击造成的后果远比虚拟置换严重,它在易受攻击的应用程序中注入实际运行的功能,旨在欺骗终端用户执行某种有害操作(如输入敏感数据),随后将它们传送给攻击者。

在一个明显的攻击中,攻击者注入的功能向用户显示一个木马登录表单,要求他们向攻击者控制的服务器提交他们自己的证书。如果由技巧熟练的攻击者实施,这种攻击还允许用户无缝登录到真正的应用程序中,确保他们不会发觉访问过程中的任何反常情况。然后,攻击者就可以自由使用受害者的证书实现自己的目的。这种类型的有效载荷非常适于用在钓鱼攻击中,向用户传送一个经过专门设计、连接可信应用程序的URL,并要求他们正常登录以访问这个URL。

另一种明显的攻击是以某种有吸引力的条件为诱饵,要求用户输入他们的信用卡信息。例如,图12-7是一个由Jim Ley设计的概念验证攻击,它利用了2004年在Google中发现的一种反射型XSS漏洞。

图12-7 一次注入木马功能的反射型XSS攻击

由于这些攻击中的URL指向真实应用程序的可信域名,如果在必要时使用有效的SSL证书,它们就比纯粹的钓鱼Web站点更有可能说服受害者提交敏感信息;后者通常位于另一个域中,而且只是克隆目标Web站点的内容。

诱使用户执行操作

如果攻击者劫持受害者的会话,那么他就可以“作为”该用户使用应用程序,并代表这名用户执行任何操作。但是,这种执行任意操作的方法并不总能达到想要的目的。它要求攻击者监控他们自己的服务器,看其是否收到被攻破的用户的会话令牌;而且,它还要求他们代表每一名用户执行相关操作。如果要向许多用户实施攻击,这种方法并不可行。而且,它在应用程序的日志中留下相当明显的痕迹,用户在调查过程中利用它可迅速确定执行未授权操作的计算机。

如果攻击者想要代表每位被攻破的用户执行一组特殊的操作,就可以采用另一种劫持会话的方法,即利用攻击有效载荷脚本执行操作。如果攻击者想要执行某个需要管理权限的操作,如修改他控制的一个账户的权限,这种方法特别有用。由于用户众多,要劫持每名用户的会话并确定其是否为管理员,可能需要付出极大的努力。一种更加有效的方法是,诱使每个被攻破的用户尝试升级攻击者账户的权限。大多数尝试都会失败,但如果一个管理用户被攻破,攻击者就能够成功提升他的权限。我们将在13.1.1节说明各种诱使其他用户执行操作的方法。

前面描述的MySpace XSS蠕虫就是这种攻击有效载荷的一个典型示例;同时,它说明这种攻击者可以毫不费力就代表大量用户执行未授权操作的严重性。这种攻击利用一系列采用Ajax技术的复杂请求(如第3章所述)来执行传播蠕虫所需的各种操作。

如果攻击者的主要目标为应用程序,并且希望在攻击时尽可能地保持隐秘,他就可以利用这种类型的XSS攻击有效载荷让其他用户执行他选择的、针对应用程序的恶意操作。例如,攻击者可以促使其他用户利用一个SQL注入漏洞在数据库的用户账户表中添加一个新的管理员用户。然后,攻击者就可以控制这个新账户,通过它执行恶意操作;但是,任何对应用程序日志的调查结论却将怀疑对象指向这名新建的用户。

利用信任关系

上文已经介绍了可被XSS利用的一种重要的信任关系:浏览器信任由发布cookie的Web站点提交的JavaScript。有时,在XSS攻击中还可以利用其他一些信任关系。

如果应用程序采用激活自动完成功能的表单,由应用程序提交的JavaScript就可以截获任何以前输入的、用户浏览器保存在自动完成缓存中的数据。通过示例化相关表单,等待浏览器自动完成它的内容,然后查询表单字段值,上述JavaScript脚本就能够窃取这些数据并将其传送至攻击者的服务器。这种攻击比注入木马功能更加强大,因为它不需要用户执行任何操作就可以截获敏感数据。

一些Web应用程序推荐或要求用户把其域名添加到浏览器的“可信站点”区域内。这种要求几乎总会造成不利影响,并意味着攻击者可以利用任何XSS类型的漏洞在受害用户的计算机上执行任意代码。例如,如果一个站点在Internet Explorer的可信站点区域内运行,那么注入以下代码将会在用户的计算机上启动Windows计算器程序。

Web应用程序通常采用包含强大方法的ActiveX控件(请参阅第13章)。一些应用程序在该控件内核实调用的Web页面确实属于正确的Web站点,力求防止第三方滥用这种控件。在这种情况下,通过XSS攻击仍然可以滥用这个控件,因为这时调用的代码可以通过控件实施信任检查。

 错误观点 “钓鱼与XSS攻击只会影响公众因特网上的应用程序。”

XSS漏洞可以影响任何类型的Web应用程序;通过一封群发电子邮件传送的针对内联网应用程序的攻击可以利用两种形式的信任关系。首先,在同事之间传送的内部电子邮件利用他们之间的社交信任关系。其次,与公众因特网上的Web服务器相比,受害者的浏览器往往会更加信任企业Web服务器。例如,如果一台计算机属于企业域的一部分,那么在访问内联网应用程序时,浏览器会默认使用较低的安全级别。

扩大客户端攻击范围

攻击者可以采用各种方式直接攻击访问一个Web站点的用户。所有这些攻击都可以通过易受攻击的应用程序中的一个跨站点脚本漏洞传送,当然,用户偶然访问的任何恶意站点也可以直接传送它们。我们会在第13章的最后详细介绍这种类型的攻击。

XSS攻击的传送机制

确定一个XSS漏洞并设计出利用它的适当有效载荷后,攻击者需要找出办法向应用程序的其他用户传送攻击。我们在前面已经讨论了几种传送方法。实际上,攻击者还可以使用其他许多传送机制。

传送反射型与基于DOM的XSS攻击

除了通过电子邮件向随机用户大量发送专门设计的URL这种明显的钓鱼向量外,攻击者还可以尝试使用以下机制传送反射型或基于DOM的XSS攻击。

在有针对性的攻击中,攻击者可以向个体目标用户或少数几名用户发送一封伪造的电子邮件。例如,可以向管理员发送一封明显由已知用户送出的电子邮件,抱怨某个特殊的URL造成错误。如果攻击者想要攻破某个特殊用户的会话(而非截取随机用户的会话),实施合理、可靠的针对性攻击往往是最有效的传送机制。有时我们也把这类攻击称为“鱼叉式钓鱼”。

可以在即时消息中向目标用户提供一个URL。

第三方Web站点上的内容与代码可用于生成触发XSS漏洞的请求。各种常见的应用程序允许用户发布数量有限的HTML标记,这些标记将按原样向其他用户显示。如果可以使用GET方法触发XSS漏洞,攻击者就可以在第三方站点上发布一个指向某恶意URL的IMG标签,任何查看以上第三方内容的用户将在不知情的情况下请求该恶意URL。

或者,攻击者可以创建自己的Web站点,在其中包含诱使用户访问的有趣内容,但也可能含有一些脚本,导致用户的浏览器向易受攻击的应用程序提出包含XSS有效载荷的请求。如果某用户登录以上易受攻击的应用程序,并且碰巧浏览了攻击者的站点,该用户访问以上易受攻击的应用程序的会话将被攻破。

建立适当的Web站点后,攻击者可以使用搜索引擎操纵技巧生成某些用户提交的访问,例如,将相关关键字放入站点内容中并使用相关表达式将其链接到相关站点。但是,这种传送机制与钓鱼攻击无关,因为攻击者的站点并未试图模仿它所针对的站点。

注意,这种传送机制使得攻击者可利用只通过POST请求触发的反射型与基于DOM的XSS漏洞。但是,利用这些漏洞,攻击者明显不能通过向受害用户发送一个简单的URL来传送一次攻击。然而,某个恶意Web站点可能包含一个HTML表单,它使用POST方法并以易受攻击的应用程序作为它的目标URL。其页面上的JavaScript或导航控件可用于提交表单,成功利用漏洞。

在另一种利用第三方Web站点的攻击中,一些攻击者付费购买许多链接至一个URL的横幅广告,该URL中包含一个针对某易受攻击的应用程序的XSS有效载荷。如果一名用户登录这个易受攻击的应用程序,并单击广告,那么他登录该应用程序的会话就会被攻破。因为许多提供商使用关键字将广告分配给与其有关的页面,有时就会出现这样的情况:一个攻击特定应用程序的广告恰巧被分配在这个应用程序的页面中。这不仅提高了攻击的可信性,而且还可以确保在攻击者实施攻击时,单击广告的用户正在使用那个易受攻击的应用程序。此外,由于目标URL现在为“本站点” URL,该攻击能够避开用于防范XSS的基于浏览器的机制(将在本章后面部分详细介绍)。另外,由于许多横幅广告提供商按点击率收费,这种技巧使得攻击者能够“买进”大量用户会话。

许多应用程序执行一种“推荐给朋友”或向站点管理员发送反馈的功能。这种功能通常允许用户生成一封电子邮件,其内容与收件人均可自由设置。攻击者能够利用这种功能,通过一封实际源自自己服务器的电子邮件传送XSS攻击,提高邮件被技术熟练的用户与反恶意软件的软件接受的可能性。

传送保存型XSS攻击

保存型XSS攻击共有两种传送机制:带内与带外传送机制。

带内传送机制适用于大多数情况,这时漏洞数据通过主Web界面提交给应用程序。用户控制的数据最终显示给其他用户的常见位置包含:

个人信息字段,如姓名、地址、电子邮件、电话等;

文档、上传文件及其他数据的名称;

提交给应用程序管理员的反馈或问题;

向其他应用程序用户传送的消息、注释、问题等;

记录在应用程序日志中,并通过浏览器显示给管理员的任何内容,如URL、用户名、HTTP Referer、 User-Agent等;

在用户之间共享的上传文件内容。

在这些情况下,只需向应用程序页面提交XSS有效载荷,然后等待受害者查看恶意代码,就可以传送XSS有效载荷。

带外传送机制适用于通过其他渠道向应用程序提交漏洞数据的情况。应用程序通过这种渠道接收数据,并最终在主Web界面生成的HTML页面中显示它。前面描述的针对Web邮件应用程序的攻击就是这种传送机制的典型示例。这种攻击向一个SMTP服务器传送恶意数据,并最终在一条HTML格式的电子邮件消息中向用户显示这些数据。

链接XSS与其他攻击

XSS漏洞有时可与其他漏洞链接在一起,造成破坏性的后果。笔者曾遇到一个应用程序,它的用户昵称中存在一个保存型XSS漏洞。这个数据的唯一用途是在用户登录后显示一条个性化欢迎消息。该昵称从不向其他应用程序用户显示,因此,最初似乎没有任何攻击向量会致使用户在编辑昵称时造成问题。在其他各点都相同的情况下,这种漏洞属于风险极低的漏洞。

但是,该应用程序还存在另一个漏洞。由于访问控制存在缺陷,任何用户都可以编辑其他用户的昵称。同样,这个问题本身并不严重。有哪个攻击者会对修改其他用户的昵称感兴趣呢?

然而,如果将这两个低风险的漏洞链接在一起,攻击者就可以完全控制应用程序。首先,攻击者只需设计一个自动攻击,在每个应用程序用户的昵称中注入一段脚本。每次用户登录应用程序,这段脚本就会执行,并将该用户的会话令牌传送到攻击者控制的服务器中。应用程序的一些用户为管理员,他们经常登录,能够创建新用户并修改其他用户的权限。攻击者只需等待一名管理员登录,劫持管理员的会话,然后升级自己的账户,获得管理权限。因此,这两个漏洞同时出现会给应用程序的安全造成极大的风险。

在另一个示例中,仅向提交它们的用户显示的数据可以通过跨站请求伪造攻击(请参阅第13章了解详细信息)进行更新。同时,应用程序中还包含保存型XSS漏洞。同样,如果单独出现,这两个漏洞的风险相对较低。但是,如果被攻击者结合在一起加以利用,它们就可能会造成严重的影响。

 错误观点 “我们不必担心低风险XSS漏洞;用户只能利用它攻击他们自己。”

如上所述,在适当的情况下,即使是明显低风险的漏洞,也会为更具破坏性的攻击打下基础。实施深层安全防御必须删除每一个已知的漏洞,无论它多么无关紧要。笔者就曾利用XSS在页面响应中插入文件浏览器对话框或AcitveX控件,侵入与目标Web应用程序绑定在一起的Kiosk模式系统。在想出办法利用细微漏洞方面,渗透测试员应该始终假定攻击者比自己更富有想象力。

查找并利用XSS漏洞

确定XSS漏洞的基本方法是使用下面这个概念验证攻击字符串:

这个字符串被提交给每个应用程序页面中的每一个参数;同时,攻击者监控它的响应,看其中是否出现相同的字符串。如果发现攻击字符串按原样出现在响应中,几乎可以肯定应用程序存在XSS漏洞。

如果仅仅是为了尽可能快地确定应用程序中存在的某种XSS漏洞,以向其他应用程序用户实施攻击,那么这个基本方法可能是最为有效的方法,因为它可以实现高度自动化,而且很少生成错误警报。但是,如果是对应用程序进行复杂的测试,从而确定尽可能多的漏洞,那么在应用基本方法的同时,还需要组合使用更加复杂的技巧。在以下几种情况下,通过基本的检测方法可能无法确定应用程序中存在的XSS漏洞。

许多应用程序实施基于黑名单的初步过滤,试图阻止XSS攻击。通常,这些过滤在请求参数中寻找<script>之类的表达式,并采取一些防御措施,如删除或编码表达式,或者完全阻止这类请求。基本检测方法中常用的攻击字符串往往被这些过滤阻止。但是,仅仅因为一个常见的攻击字符串被阻止,并不能证明一个可利用的漏洞不存在。如后文所述,在有些情况下,不使用<script>标签,甚至不使用"< >和/这些常被过滤掉的字符,也可以利用XSS漏洞。

许多应用程序实施的防XSS过滤存在缺陷,可以通过各种方法避开。例如,假设在处理用户输入前,应用程序删除其中出现的所有<script>标签。这意味着基本方法中使用的攻击字符串将不会在应用程序的响应中返回。但是,以下一个或几个字符串可轻易避开过滤,成功利用XSS漏洞:

尝试访问

http://mdsec.net/search/28/

http://mdsec.net/search/36/

http://mdsec.net/search/21/

注意,在这些情况下,在服务器的响应中,输入的字符串返回前,可能经过净化、编码或其他形式的修改,因而还不足以实现对XSS漏洞的利用。这时,提交一个特殊字符串并检查它是否在服务器的响应中出现的基本检测方法将无法成功发现漏洞。

当利用基于DOM的XSS漏洞时,攻击有效载荷并不在服务器的响应中返回,而是保存在浏览器DOM中,并可被客户端JavaScript访问。同样,在这种情况下,提交一个特殊字符串并检查它是否在服务器的响应中出现的基本检测方法将无法成功发现漏洞。

查找并利用反射型XSS漏洞

要探查反射型XSS漏洞,最可靠的方法是系统性地检查在解析应用程序(请参阅第4章)过程中确定的所有用户输入进入点,并遵循以下步骤。

在每个进入点提交一个良性字母字符串。

确定此字符串“反射”在应用程序响应中的所有位置。

对于每个反射,确定显示反射型数据时的语法上下文。

提交针对反射的语法上下文而修改的数据,尝试在响应中引入任意脚本。

如果反射型数据被阻止或净化,导致脚本无法执行,则尝试了解并避开应用程序的防御性过滤。

确认用户输入的反射

检测反射型XSS漏洞的最可靠方法的初始步骤与前面描述的基本方法类似。

渗透测试步骤

(1)选择任意一个字符串,该字符串不曾出现在应用程序的任何地方,而且其中仅包含字母字符,因此不可能受到针对XSS过滤的影响。例如:

提交这个字符串,以其作为每个页面的每一个参数,且每次只针对一个参数。

(2)监控应用程序的响应,看其中是否出现同一个字符串。记下参数值被复制到应用程序响应中的每一个参数。这些参数不一定容易受到攻击,但需要对它们进行深入分析,其过程将在后文中描述。

(3)注意,必须测试所有GET与POST请求,检查URL查询字符串与消息主体中的每一个参数。虽然有少数XSS漏洞传送机制只能通过一个POST请求触发,但仍有可能对漏洞加以利用,如前文所述。

(4)任何时候,一旦在POST请求中发现XSS,应使用Burp中的“更改请求方法”(change request method)选项确定是否可以通过GET请求实施相同的攻击。

(5)除标准的请求参数外,还应该检测HTTP请求消息头内容被应用程序处理的每一种情况。有一种常见的XSS漏洞出现在错误消息中,这时Referer与User-Agent消息头之类的数据项被复制到消息的内容中。这些消息头是传送反射型XSS攻击的有效工具,因为攻击者可以使用一个Flash对象诱使受害者提出一个包含任意HTTP消息头的请求。

测试引入脚本的反射

渗透测试员必须手动检查已确定的每一个反射型输入实例,以核实其是否确实可被利用。在响应中包含反射型数据的每个位置,都需要确认该数据的语法特点。这时,渗透测试员必须找到某种修改输入的方法,以便在将输入复制到应用程序响应中的相同位置时,任何脚本都能够得以执行。下面分析这方面的一些示例。

例1:标签属性值

假设返回的页面中包含以下脚本:

很明显,利用XSS的一种方法是终止包含字符串的双引号,结束<input>标签,然后通过其他方法引入JavaScript脚本(使用<script>等)。例如:

在这种情况下,另一种可以避开某些输入过滤的利用方法,是在<input>标签内注入一个包含JavaScript的事件处理器。例如:

例2: JavaScript字符串

假设返回的页面中包含以下脚本:

这时,受控制的字符串被直接插入到现有的一段脚本中。要利用XSS,可以终止字符串周围的单引号,用一个分号终止整个语句,然后直接处理想要执行的JavaScript。例如:

注意,因为已经终止了一个被引用的字符串,为阻止JavaScript解释器出现错误,必须在注入的代码后使用有效的语法确保脚本继续正常执行。在这个示例中,变量foo被声明,另一个引用字符串被打开,它们将被紧随在字符串后面的代码终止。另一种经常有效的方法是使用//结束输入,将剩下的脚本当做注释处理。

例3:包含URL的特性

假设返回的页面中包含以下脚本:

这时,受控制的字符串插入到一个<a>标签的href属性中。在一些浏览器中,这个属性可能包含一个使用javascript:协议的URL,从而可以使用以下脚本直接利用XSS:

如前所述,因为输入将反射到标签属性中,因此这时还可以注入一个事件处理器。

要向当前所有的浏览器实施攻击,可以同时使用一个无效的图像名称与一个onerror事件处理器:

 提示 和其他攻击一样,渗透测试员必须对请求中出现的任何特殊字符进行URL编码,包括& = + ;和空格。

渗透测试步骤

对于在前面步骤中记下的每一个潜在的XSS漏洞,采取以下措施。

(1)检查HTML源代码,确定受控制的字符串的位置。

(2)如果字符串出现在几个位置,应将每个位置当做一个潜在的漏洞,分别进行分析。

(3)根据用户控制的字符串在HTML中的位置,确定需要如何对其进行修改以使任意JavaScript得以执行。通常,有大量方法可成为传送攻击的有效工具。

(4)向应用程序提交设计的字符串,测试它是否有用。如果设计的字符串仍然按原样返回,表示应用程序存在XSS漏洞。使用一段概念验证脚本显示一个警报对话框,重复检查语法是否正确,并确定响应显示时,对话框是否出现在浏览器中。

探查防御性过滤

通常情况下最初提交的攻击字符串并不会被服务器按原样返回,因而无法成功执行注入的JavaScript。如果是这样,不要放弃!接下来应该确定服务器对输入进行了哪些处理。主要有以下3种可能的情况。

应用程序或者Web应用程序防火墙保护的应用程序发现一个攻击签名,完全阻止了输入。

应用程序已经接受了输入,但对攻击字符串进行了某种净化或编码。

应用程序把攻击字符串截短至某个固定的最大长度。

我们将分别分析每一种情况,并讨论如果通过每种方法避开应用程序设立的障碍。

避开基于签名的过滤

在第一种类型的过滤中,应用程序通常会对攻击字符串做出与无害字符串截然不同的响应,例如,通过一条错误消息,甚至会指出发现一个可能的XSS攻击,如图12-8所示。

图12-8 一条由ASP.NET反XSS过滤器生成的错误消息

如果出现这种情况,那么接下来,应该确定输入中的哪些字符或表达式触发了过滤。一种有效的方法是轮流删除字符串的不同部分,看输入是否仍然被阻止。通常,使用这种方法可迅速查明是否是某个特殊的表达式(如<script>)造成请求被阻止。如果确实如此,那么需要对过滤进行测试,看是否有任何避开过滤的办法。

有各种不同的方法可以在HTML页面中引入脚本代码,这些方法通常能够避开基于签名的过滤。因此,测试员要么找到引入脚本的其他方法,要么使用浏览器接受的略显畸形的语法。在这一节中,我们将介绍各种执行脚本的不同方法,然后说明一系列可用于避开常用过滤的技巧。

引入脚本代码的方法

有4种不同的方法可用于在HTML页面中引入脚本代码。我们将逐一介绍这些方法,并提供一些可用于成功避开基于签名的输入过滤的特殊示例。

 注解 浏览器对于各种HTML和脚本语法的支持各不相同。个体浏览器的行为也往往会随着新版本的发布而发生改变。因此,任何针对个体浏览器行为的“明确”指南也很快会过时。但是,从安全的角度看,应用程序需要在所有当前和最新版本的常用浏览器中可靠运行。如果XSS攻击只能通过仅由少数用户使用的特定浏览器进行传送,这仍然构成一个漏洞,应对其予以修复。到本书截稿时止,本章中提供的所有示例至少能够在某种注流浏览器上运行。

为便于参考,本章于2011年3月撰写,所有描述的攻击至少能够在以下一种浏览器上实施:

Internet Explorer版本8.0.7600.16385 ;

Firefox 版本 3.6.15。

脚本标签

除直接使用<script>标签外,还可以通过各种方法、使用复杂的语法来隐藏标签,从而避开某些过滤:

上例中的基于Base64的字符串为:

√ 事件处理器

有大量事件处理器可与各种标签结合使用,以用于执行脚本。以下是一些较为少见的示例,可在不需要任何用户交互的情况下执行脚本:

HTML5使用事件处理器提供了大量向量。这包括使用autofocus属性自动触发之前需要用户交互的事件:

它允许在结束标签中使用事件处理器:

最后,HTML5还通过事件处理器引入了新标签:

√ 脚本伪协议

脚本伪协议可用在各种位置,以在需要URL的属性中执行行内脚本。以下是一些示例:

虽然上面的示例主要使用的是javascript伪协议,但是,还可以在Internet Explorer上使用vbs协议,如本章后面部分所述。

和事件处理器一样,HTML5也提供一些在XSS攻击中使用脚本伪协议的新方法:

在针对输入过滤进行攻击时,新的event-source标签特别有用。与之前的任何HTML5标签不同,它的名称中包含一个连字符,因此,使用这个标签可以避开传统的、认为标签名称只能包含字母的基于正则表达式的过滤。

√ 动态求值的样式

一些浏览器支持在动态求值的CSS样式中使用JavaScript。以下示例可以在IE7及其早期版本上执行,如果在兼容模式下运行,还可以在后续版本上执行:

最新版本的IE不再支持上述语法,因为这些语法只能用在XSS攻击中。但是,在最新版本的IE中,使用以下请求可以达到同样的效果:

使用Firefox浏览器可以通过moz-binding属性实施基于CSS的攻击,但是,由于应用程序已对这一功能实施了限制,现在已经无法通过它来实施XSS攻击。

√ 避开过滤:HTML

在前面几节中,我们介绍了各种可用于在HTML页面中执行脚本代码的方法。许多时候,你会发现,通过采用不同的、较为少见的脚本执行方法,就可以避开基于签名的过滤。如果这种方法失败,你就需要寻找其他隐藏攻击的方法。通常,你可以引入过滤器接受的异常语法,并使浏览器接受返回的输入。在本节中,我们将介绍各种对HTML请求进行模糊处理以避开常见的过滤的方法。本节对JavaScript和VBScript语法应用相同的原则。

旨在阻止XSS攻击的基于签名的过滤通常采用正则表达式或其他技巧来确定关键的HTML组件,如标签括号、标签名称、属性名称和属性值。例如,过滤器可能会阻止包含使用已知可用于引入脚本的特殊标签或属性名称的HTML的输入,或试图阻止以脚本伪协议开头的属性值。通过以一种或多种浏览器接受的方式在HTML中的关键位置插入不常见的字符,可以避开其中的许多过滤。

我们来了解一下这种技巧的用法,以下面这段简单的脚本为例:

可以通过各种方式修改这段脚本,并使它至少可在一个浏览器中运行。下面我们将分别介绍这些方法。实际上,你可能需要在一次攻击中结合利用其中的几种技巧,以避开更加复杂的输入过滤。

√ 标签名称

从起始标签名称开始,只需改变所使用字符的大小写,即可避开最简单的过滤:

更进一步,可以在任何位置插入NULL字节:

(在以上示例中,[%XX]表示十六进制ASCII代码XX的原义字符。在向应用程序实施攻击时,通常会使用字符的URL编码形式。在查看应用程序的响应时,需要在其中寻找已解码的原义字符。)

 提示 NULL字节技巧可用在Internet Explorer上的HTML页面的任何位置。在XSS攻击中灵活使用NULL字节通常可以快速避开不探查IE行为的基于签名的过滤。

事实证明,使用NULL字节可以有效避开配置为阻止包含已知攻击字符串的请求的Web应用程序防火墙(WAF)。为了提高性能,WAF通常以本地代码编写,因此,NULL字节将终止在其中出现的字符串。这样,WAF就无法发现NULL字节之后的恶意有效载荷(请参阅第16章了解详细信息)。

更进一步,如果你对上面示例中的标签名称稍做修改,就可以使用任意标签名称引入事件处理器,从而避开仅仅阻止特定标签名称的过滤:

有时,可以引入不同名称的新标签,但却找不到使用这些标签直接执行代码的方法。在这些情况下,可以使用一种称为“基本标签劫持”的技巧来实施攻击。<base>标签用于指定一个URL,浏览器应使用该URL解析随后在页面中出现的任何相对URL。如果可以引入一个新的<base>,并且页面执行反射点后的任何使用相对URL的<script>,则你就可以指定一个指向受你控制的服务器的基本URL。当浏览器加载在HTML页面的剩余部分指定的脚本时,这些脚本将从指定的服务器加载,但仍然能够在调用它们的页面中执行。例如:

根据规范,<base>标签应出现在HTML页面的<head>部分。但是,一些浏览器,如Firefox,允许<base>标签出现在页面的任何位置,这显著扩大了这种攻击的范围。

√ 标签名称后的空格

一些字符可用于替代标签名称与第一个属性名称之间的空格:

需要注意的是,即使在实施攻击时不需要任何标签属性,仍然应始终在标签名称后面添加一些多余的内容,因为这样做可以避开一些简单的过滤:

√ 属性名称

也可以在属性名称中使用上述NULL字节技巧。这样做可以避开许多试图通过阻止以on开头的属性名称来阻止事件过滤器的简单过滤:

√ 属性分隔符

在最初的示例中,属性值之间并未分隔开来,因而需要在属性值后面插入一些空格,表示属性值已结束,以便于添加其他属性。属性可以选择使用双引号或单引号进行分隔,或在IE上使用重音符分隔:

前面的示例提供了另一种方法,可用于避开一些检查以on开头的属性名称的过滤器。如果过滤器不知道重音符被用作属性分隔符,它会将下面的示例视为仅包含一个属性,其名称不再为事件处理器的名称:

通过使用引号分隔的属性,并在标签名称后面插入异常字符,就可以设计出不需要使用任何空格的攻击,从而避开一些简单的过滤:

尝试访问

http://mdsec.net/search/69/

http://mdsec.net/search/72/

http://mdsec.net/search/75/

√ 属性值

在属性值中,可以使用NULL字节技巧。还可以使用HTML编码的字符,如下所示:

在进一步处理属性值之前,浏览器会对其进行HTML解码,因此,可以使用HTML编码对脚本代码进行模糊处理,从而避开任何过滤。例如,以下攻击避开了许多试图阻止JavaScript伪协议处理器的过滤:

在使用HTML编码时,值得注意的是,浏览器接受规范的各种变体,甚至可能忽略过滤器“意识到”的HTML编码问题。可以使用十进制和十六进制格式,添加多余的前导零,并省略结尾的分号。以下示例至少可以用在一种浏览器中:

√ 标签括号

有些时候,通过利用奇怪的应用程序或浏览器行为,甚至可以使用无效的标签括号,并且仍然使浏览器按攻击所需的方式处理相关标签。

一些应用程序在应用输入过滤后还执行不必要的URL解码,因此,请求中的以下输入

被应用程序服务器进行URL解码,然后将以下输入传递给应用程序:

其中并不包含任何标签括号,因此不会被输入过滤阻止。但是,应用程序随后会执行第二次URL解码,因此输入将变为:

该输入会回显给用户,导致攻击得以实施。

如第2章所述,如果应用程序框架基于字形和发音的相似性,将不常见的Unicode字符“转换”为它们最接近的ASCII字符,这时可能会出现与上述示例类似的情况。例如,以下输入使用Unicode双角引号(%u00AB和%u00BB),而不是标签括号:

应用程序的输入过滤可能会允许该输入,因为其中并不包含任何有问题的HTML。但是,如果应用程序框架在输入被插入到响应中时将引号转换为标签字符,攻击将取得成功。事实证明,由于开发者的疏忽,大量应用程序都易于受到这种攻击。

一些输入过滤通过简单地匹配起始和结束尖括号,提取内容,并将其与标签名称黑名单进行比较来识别HTML标签。在这种情况下,可以通过使用多余的括号(如果浏览器接受)来避开过滤:

某些情况下,可以利用浏览器的HTML解析器的异常行为来实施攻击,从而避开应用程序的输入过滤。例如,以下HTML使用了ECMAScript for XML(E4X),其中并不包含有效的起始脚本标签,但仍然可以在当前版本的Firefox中执行包含的脚本:

 提示 在上述用于避开过滤的各种技巧中,实施攻击的HTML虽然存在缺陷,但仍被客户端浏览器所接受。由于有大量相当合法的网站包含并不严格遵循标准的HTML,这导致浏览器接受各种各样存在问题的HTML。在呈现页面之前,这些浏览器会在后台有效地修复相关错误。通常,在反常情况下尝试调整攻击方式时,查看浏览器基于服务器的具体响应构建的虚拟HTML会有所帮助。在Firefox中,可以使用WebDeveloper工具,该工具提供的“查看生成的源”(View Generated Source)功能正好可用于完成上述任务。

√ 字符集

有些时候可以使用一种非常强大的方法,致使应用程序接受攻击有效载荷的非标准编码,从而避开各种类型的过滤。下面的示例说明了字符串<script>alert(document.cookie)</script>的一些非标准编码表示法:

UTF-7

US-ASCII

UTF-16

这些编码后的字符串可避开许多常见的反XSS过滤。实施成功攻击的挑战在于如何使浏览器使用所需的字符集来解释响应。如果你控制了HTTP Content- Type消息头或其对应的HTML元标签,就可以使用非标准字符集避开应用程序的过滤,使浏览器按照需要的方式解释有效载荷。一些应用程序在某些请求中提交charset参数,允许直接设置在应用程序的响应中使用的字符集。

如果应用程序默认使用多字节字符集,如Shift-JIS,这时,可以通过提交在所采用的字符集中具有特殊意义的字符来避开某些输入过滤。例如,假设应用程序的响应中返回了以下两段用户输入:

对于input1,应用程序会阻止包含引号的输入,以防止攻击者终止引用的属性。对于 input2,应用程序会阻止包含尖括号的输入,以防止攻击者使用任何HTML标签。这种过滤似乎较为可靠,但是,攻击者可以通过使用以下两个输入来实施攻击:

在Shift-JIS字符集中,各种原始字节值(包括0xf0)用于表示由该字节及随后的字节组成的2字节字符。因此,浏览器在处理input1时,0xf0字节后面的引号将被解释为一个2字节字符的一部分,而不是属性值的分隔符。HTML解析器将继续运行,直到到达input2提供的引号位置(该引号终止了属性),从而允许攻击者提交将作为其他标签属性解释的事件处理器:

在广泛使用的字符集UTF-8中发现这种漏洞时,浏览器供应商发布了一个补丁,阻止了相关攻击。但是,这些攻击当前仍然能够在某些浏览器上成功实施,它们主要针对是其他一些较少使用的多字节字符集,包括Shift-JIS、EUC-JP和BIG5。

避开过滤:脚本代码

某些情况下,可以找到办法来操纵反射型输入,从而在应用程序的响应中插入脚本。但是,可能会遇到各种其他障碍,无法执行实施有效攻击所需的代码。这时,遇到的过滤通常会试图阻止你使用某些JavaScript关键字和其他表达式。它们还可能阻止有用的字符,如引号、括号和圆点。

和使用HTML对攻击进行模糊处理一样,也可以通过使用各种技巧来修改所需的脚本代码,以避开常见的输入过滤。

√ 使用 JavaScript转义

JavaScript允许各种字符转义,可以通过这种方式避免包含原义格式的表达式。

Unicode转义可用于表示JavaScript关键字中的字符,从而避开许多类型的过滤:

如果能够使用eval命令(通过利用之前介绍的技巧来转义它的某些字符),就可以将其他命令以字符串格式传送给eval命令,从而执行这些命令。这样,就可以利用各种字符串操纵技巧来隐藏执行的命令。

在JavaScript字符串中,可以使用Unicode转义、十六进制转义和八进制转义:

此外,字符串中的多余转义字符将被忽略:

√ 动态构建字符串

可以使用其他技巧来动态构建在攻击中使用的字符串:

上面的示例可在Firefox上执行,可以通过它解码Base64编码的命令,然后将其传递给eval。

√ 替代eval的方法

如果无法直接调用eval命令,可以通过其他方法以字符串格式执行命令:

√ 替代圆点

如果圆点被阻止,可以使用以下方法解引用:

√ 组合多种技巧

到现在为止,我们介绍的各种技巧通常都可以组合使用,以对攻击进行多层模糊处理。在HTML标签属性中使用JavaScript(通过事件处理器、脚本伪协议或动态求值的样式)的情况下,可以将这些技巧与HTML编码组合使用。浏览器会对标签属性值进行HTML解码,然后再解释其中包含的JavaScript。在下面的示例中,alert中的e字符使用Unicode转义方法进行了转义,并且Unicode转义中使用的反斜线经过了HTML编码:

当然,还可以对onerror属性值中的任何其他字符进行HTML编码,以进一步隐藏攻击:

使用这种技巧可以避开许多针对JavaScript代码的过滤,因为可以避免使用任何JavaScript关键字或其他语法,如引号、句号和括号。

√ 使用 VBScript

常见的XSS攻击通常主要通过JavaScript来实施,但是,在Internet Explorer上,还可以使用VBScript语言。该语言使用不同的语法和其他属性,攻击者可以对其加以利用,以避开许多仅针对JavaScript的输入过滤。

可以通过各种方式插入VBScript代码,如下所示:

无论是哪一种情况,都可以使用vbscript(而不是vbs)来指定语言。请注意,最后一个示例使用了MsgBox+1,以避免使用空白符,因而也不需要在属性值周围加上引号。这样做之所以可行,是因为+1有效地给“空白”加上了数字1,因此表达式的求值结果为1;随后,这一结果被传递给MsgBox函数。

值得注意的是,如前面的示例所示,在VBScript中,一些函数无须使用括号即可调用。如果过滤机制认为必须使用括号才能访问函数,就可以通过使用VBScript来避开这些过滤。

此外,与JavaScript不同,VBScript语言不区分大小写,因此,可以在所有关键字和函数名称中使用大写和小写字符。如果你所攻击的应用程序函数会修改输入的大小写,如将其转换为大写,这时VBScript就特别有用。虽然应用程序将输入转换为大写的做法主要是出于功能而不是安全考虑,但这样做却可以挫败使用JavaScript代码的XSS攻击,因为转换为大写后,代码将无法执行。与之相反,使用VBScript代码的攻击仍然有效:

√ 组合JavaScript和VBScript

为进一步增加攻击的复杂程度,并避开某些过滤,可以从JavaScript中调用VBScript,或从VBScript中调用JavaScript:

甚至可以嵌套这些调用,并根据需要使用任何一种语言:

如上所述,VBScript不区分大小写,即使输入被转换为大写,你的代码仍然可以执行。在这些情况下,如果你确实希望调用JavaScript函数,可以使用VBScript中的字符串操纵函数用所需的大/小写构建一个命令,并使用JavaScript执行该命令:

√ 使用经过编码的脚本

在Internet Explorer上,可以使用Microsoft的定制脚本编码算法来隐藏脚本的内容,从而避开某些输入过滤:

这种编码最初旨在用于防止用户通过查看HTML页面的源代码来轻松访问客户端脚本。此后,该算法被人们破解,而且,可以通过各种工具和网站来解码经过编码的脚本。通过Microsoft的旧版Windows中的命令行实用工具srcenc,你可以对自己的脚本进行编码,以用于实施攻击。

5.避开净化

当尝试利用潜在的XSS漏洞时,避开净化可能是所有障碍中最为常见的一种。这时,应用程序对攻击字符串执行某种净化或编码,使其变得无害,防止它执行JavaScript。

对传送攻击所需的某些关键字符进行HTML编码(因此 < 变成,> 变成)是应用程序实行数据净化最常见的做法。其他情况下,应用程序可能会完全删除某些字符或表达式,试图清除输入中的恶意内容。

如果遇到这种防御,首先应查明应用程序净化哪些字符与表达式,以及是否仍然可通过剩下的字符实施攻击。例如,如果输入的数据被直接插入到现有的一段脚本中,那么可能不需要使用任何HTML标签字符。或者,如果应用程序从输入中删除<script>标签,则可以通过适当的事件处理程序使用其他标签。这时,测试员应考虑采用之前介绍的用于避开基于签名的过滤的各种技巧,包括多层编码、空字节、非标准语法以及经过模糊处理的脚本代码。通过以上述各种方式修改输入,就可以设计出不包含任何被过滤净化的字符或表达式的攻击,因而能够成功避开过滤。

如果只有使用已被净化的输入才能实施攻击,这时就需要测试净化过滤的效率,以确定是否可找到任何避开过滤的方法。

如第2章所述,净化过滤中往往存在一些错误。一些字符串操作API包含仅替换第一个匹配的表达式实例的方法,有时,这些方法易于与那些替换所有实例的方法相混淆。因此,如果输入中的<script>被删除,则应尝试以下输入,以查看是否所有实例均被删除:

在这种情况下,还应检查过滤是否以递归方式执行净化:

此外,如果过滤对输入执行多个净化步骤,则应检查是否可以对这些步骤之间的顺序或相互关系加以利用。例如,如果过滤递归删除<script>,然后递归删除<object>,以下攻击或许能够取得成功:

当在现有的一段脚本中注入一个引用字符串时,应用程序经常在注入的引号字符前插入反斜线字符。应用程序这样做是为了对引号进行转义,阻止攻击者终止字符串或注入任意脚本。在这种情况下,应该始终核实反斜线字符本身是否被转义。如果其未被转义,那么这种过滤就可以轻易避开。例如,如果能够控制下面的foo值:

那么就可以注入

这段代码生成如下响应,其中含有受控制的脚本。注意,必须使用JavaScript注释符号//将剩下的脚本作为注释处理,防止应用程序自己的字符串分隔符造成语法错误。

如果发现反斜线字符被正确转义,但尖括号却按原样返回,那么攻击者可以使用以下攻击字符串:

这样做可废弃应用程序中原来的脚本,并在其后注入一段新的脚本。攻击之所以能够成功,是因为浏览器在解析植入的JavaScript之前,优先解析HTML标签。

此时,虽然原来的脚本中包含一个错误,但这无关紧要,因为浏览器会跳过这个错误,继续执行注入的脚本。

尝试访问

http://mdsec.net/search/48/

http://mdsec.net/search/52/

 提示 在前面的两个攻击中,即使攻击者能够控制一段脚本,但由于应用程序对单引号或双引号进行了转义,他也无法使用它们,但可以使用string.fromCharCode技巧,不用分隔符创建字符串。

如果注入的脚本位于事件处理程序之内,而非完整的脚本块,则可以对引号进行HTML编码,以避开应用程序的净化,并使受控制的字符串免于被过滤。例如,如果可以控制以下输入中的foo值:

并且应用程序正确转义输入中的引号和反斜线,则可以成功实施以下攻击:

这导致以下响应,由于一些浏览器在将事件处理程序作为JavaScript执行之前会执行HTML解码,因此该攻击取得成功:

应对用户输入进行HTML编码来防范XSS攻击,对于这一常规建议,应注意以下事实:在作为JavaSrcipt执行之前,事件处理器会被HTML解码。在这种情况下,进行HTML编码并不一定能够阻止攻击。攻击者甚至可以利用这种方法避开其他防御机制。

6.突破长度限制

当应用程序把输入截短为一个固定的最大长度时,有三种建立攻击字符串的方法。

第一种相当明显的方法是尝试使用最短可能长度的JavaScriptAPI,删除那些通常包含在内但并不完全必要的字符,缩短攻击有效载荷。例如,如果注入现有的一段代码,下面的28字节命令将把用户的cookie传送至主机名为a:的服务器。

另外,如果直接注入HTML,那么下面这个30字节的标签将从主机名为a:的服务器加载并执行一段脚本。

在因特网上,这些示例明显需要进行扩展,在其中包含一个有效的域名或IP地址。但是,内部企业网络实际有可能使用一台WINS名为a的机器作为接收服务器。

 提示 可以使用Dean Edward的JavaScript packer工具(见http://dean.edwards.name/packer/)删除不必要的空白符,尽可能地缩短某一段脚本。这个工具还可将脚本转换成单独一行,方便插入到一个请求参数中。

第二种更加强大的突破长度限制的技巧是将一个攻击有效载荷分布到几个不同的位置,用户控制的输入在这里插入到同一个返回页面中。以下面的URL为例:

https://wahh-app.com/account.php?page_id=244&seed=129402931&mode=normal

它将返回一个包含以下内容的页面:

假设应用程序对每个字段实施了长度限制,以阻止在其中插入有效的攻击字符串。但是攻击者仍然可以使用以下URL将一段脚本分布到他所控制的三个位置,从而传送一个有效的攻击字符串:

这个URL的参数值植入到页面中后,生成如下脚本:

最终得到的HTML完全有效,等同于加粗显示的部分。其中的源代码块已成为JavaScript注释(包含在/*与*/之间),因此被浏览器忽略。这样,注入的脚本被执行,就好像它被完整地插入到页面的某一个位置一样。

提示 这种将一个攻击有效载荷分布到几个字段中的技巧,有时还可用于避开其他类型的防御过滤。应用程序经常对单独一个页面的不同字段执行不同的数据确认与净化。在前面的示例中,假设page_id与mod参数的最大长度为12个字符。由于这些字段如此地短,因此应用程序的开发者没有对其实施任何XSS过滤。另一方面,seed参数的长度没有限制,因此应用程序对其实施严格的过滤,以防止攻击者在其中注入"<或>字符。在这种情况下,尽管开发者实施了过滤,但攻击者不使用任何被阻止的字符,仍然能够在seed参数中插入一段任意长度的脚本,因为注入到周围字段中的数据可以建立JavaScript环境。

第三种在某些情况下非常有效的突破长度限制的技巧是,将一个反射型XSS漏洞“转换”成一个基于DOM的漏洞。例如,在最初的反射型XSS漏洞中,如果应用程序对复制到返回页面中的message参数设置长度限制,那么就可以注入以下45字节的脚本,它对当前URL中的片断字符串(fragment string)求值。

通过在易于受到反射型XSS攻击的参数中注入这段脚本,可以在生成的页面中造成一个基于DOM的XSS漏洞,从而执行位于片断字符串中的另一段脚本,它不受应用程序过滤的影响,可为任意长度。例如:

以下为上述示例的较短版本,此示例在多数情况下可以运行:

在这个版本中,整个URL经过URL解码,然后传递给eval命令。整个URL将作为有效的JavaScript执行,因为http:协议前缀作为代码标签,协议前缀后面的//则作为单行注释,%OA经过URL解码后将变为换行符,表示结束注释。

7.实施有效的XSS攻击

通常,在探查潜在的XSS漏洞,以了解并避开应用程序的过滤机制时,你往往是在浏览器以外进行测试,也就是使用Burp Repeater之类的工具重复发送相同的请求,每次对请求进行略微修改,然后测试这种修改对响应的影响。某些情况下,在以这种方式创建概念验证攻击后,你可能还需要完成任务才能针对其他应用程序用户实施有效攻击。例如,其他用户的请求中的XSS进入点(如cookie或Referer消息头)可能难以控制;或者目标用户可能使用的是内置了防范反射型XSS攻击功能的浏览器。在这一节中,我们将介绍你在实施有效的XSS攻击时可能遇到的各种挑战及如何应对这些挑战。

将攻击扩展到其他应用程序页面

假如你所确定的漏洞位于你不感兴趣的应用程序区域,只影响未经过验证的用户,而其他区域则包含真正敏感的数据和你希望攻破的功能。

通常,在这种情况下,设计一个可以通过应用程序的某个区域中的XSS漏洞传送,并且在用户的浏览器中持续存在的攻击有效载荷,就可以攻破同一个域中的目标数据或功能。

要实现上述目的,一个简单的办法是创建一个包含整个浏览器窗口的iframe,然后在该iframe中重新加载当前页面。在用户浏览站点并登录到通过验证的区域时,注入的脚本将始终在顶层窗口中运行。这样,你就能够钩住子iframe中的导航事件和表单提交,监视iframe中显示的所有响应内容,当然也能够在适当的时候劫持用户的会话。在支持HTML5的浏览器中,当用户在页面间移动时,脚本甚至可以使用window.history.pushState()函数在地址栏中设置适当的URL。

请参阅以下URL了解这种攻击的一个示例:

http://blog.kotowicz.net/2010/11/xss-track-how-to-quietly-track-whole.html

 错误观点 “我们不用担心站点中未通过验证部分的XSS漏洞,攻击者不可能利用它们来劫持会话。”

因为两方面的原因,这种看法并不正确。首先,应用程序未通过验证部分的XSS漏洞可被攻击者用于直接攻破验证用户的会话。因此,未通过验证部分的反射型XSS漏洞比通过验证部分的这类漏洞更严重,因为潜在受害者的范围更广。其次,即使用户尚未通过验证,攻击者仍然可以通过提交几个请求,在受害者的浏览器中注入某种木马功能,等待用户登录,然后劫持用户的会话。如第13章所述,攻击者甚至可能会使用以JavaScript编写的键盘记录器来捕获用户的密码。

修改请求方法

假如你确定的XSS漏洞使用POST请求,但实施攻击的最便捷的方法需要使用GET请求——例如,提交一个论坛贴子,其中包含针对易受攻击的URL的IMG标签。

在这种情况下,我们有必要进行判定,如果将POST请求转换为GET请求,应用程序是否对请求进行相同的处理。许多应用程序接受以上任何一种请求。

在Burp Proxy中,可以使用上下文菜单中的“更改请求方法”(Change Request Method)命令将任何请求在GET与POST方法之间切换。

 错误观点 “这个XSS漏洞无法被利用。因为攻击者无法通过GET请求实施攻击。”

如果一个反射型XSS漏洞只能使用POST方法加以利用,应用程序仍然会受到各种攻击传送机制的影响,包括使用恶意第三方Web站点的传送机制。

有时,把使用GET方法的攻击转换成使用POST方法的攻击可能会避开某些过滤。许多应用程序在整个应用程序中执行某种常规过滤,阻止已知的攻击字符串。如果一个应用程序希望收到使用GET方法的请求,它可能只对URL查询字符串执行这种过滤。将请求转换为使用POST方法就可以完全避开这种过滤。

通过cookie利用XSS漏洞

一些应用程序包含反射型XSS漏洞,攻击这种漏洞的进入点在请求cookie中。在这种情况下,可以通过各种技巧来利用这种漏洞:

和修改请求一样,应用程序可能允许你使用与cookie同名的URL或主体参数来触发漏洞。

如果应用程序包含任何可用于直接设置cookie值的功能(例如,基于提交的参数值设置cookie的首选项页面),则你可以设计一个跨站点请求伪造攻击,在受害者的浏览器中设置所需的cookie。然后,再诱使受害者提出以下两个请求:其中一个请求用于设置包含XSS有效载荷所需的cookie,另一个请求则用于请求以危险的方式处理cookie值的功能。

之前,人们已经在浏览器扩展技术(如Flash)中发现各种漏洞,这使攻击者可以使用任意HTTP消息头提交跨域请求。当前,至少有一种此类漏洞广为人知,但尚未修复。因此,可以利用浏览器插件中的某个这种类型的漏洞来提交跨域请求,在其中包含任意旨在触发漏洞的cookie消息头。

如果上述任何方法都无法成功实施攻击,你可以利用相同(或相关)域中的任何其他反射型XSS漏洞使用所需值设置一个永久性cookie,持续对受害用户进行攻击。

通过Referer消息头利用XSS漏洞

一些应用程序包含只能通过Referer消息头触发的XSS漏洞。利用受他们控制的Web服务器,攻击者可以相当轻松地利用这些漏洞。例如,攻击者可以诱使受害者请求他们的服务器上的URL,该URL中包含针对易于攻击的应用程序的适当XSS有效载荷。然后,攻击者的服务器将返回一个响应,以请求上述URL,而攻击者的有效载荷就包含在此请求的Referer消息头中。

在某些情况下,只有在Referer消息头包含与易受攻击的应用程序同属一个域的URL时,XSS漏洞才会触发。这时,可以利用应用程序中的任何现成的重定向功能来实施攻击。为此,你需要构建一个指向该重定向功能的URL,在其中包含XSS攻击的有效载荷,并使其重定向到易于攻击的URL。这种攻击能否成功,取决于该功能使用的重定向方法,以及当前浏览器在进行上述重定向时是否会更新Referer消息头。

通过非标准请求和响应内容利用XSS漏洞

目前,有越来越多的复杂应用程序采用不包含传统的请求参数的Ajax请求。而在此前,请求通常包含XML和JSON格式的数据,或采用各种序列化方案。因此,针对这些请求的响应往往包含同种或其他格式的数据,而不是HTML。

与这些请求和响应相关的服务器端功能大多会表现出类似于XSS的行为。正常情况下,应用程序会按原样返回表明漏洞确实存在的请求有效载荷。

在这种情况下,仍然可以利用这种行为来实施XSS攻击。为此,需要满足以下两个条件:

你需要想办法使目标用户提出所需的跨域请求;

你需要以某种方式操纵响应,以便它在到达浏览器时执行你的脚本。

满足这两个条件并不容易。首先,相关请求通常由JavaScript使用XMLHttpRequest提出(请参阅第3章了解详细信息)。默认情况下,这种方法并不能用于提出跨域请求。虽然HTML5正在对XMLHttpRequest进行修改,以便于站点指定其他可能与它们交互的域,但是,如果你能够找到允许第三方交互的目标,就可以采用更简单的方法来攻破该目标(请参阅第13章了解详细信息)。

其次,在任何攻击中,应用程序返回的响应均由受害者的浏览器直接处理,而不是由定制脚本按原样处理。因此,响应将包含任何非HTML格式的数据(通常使用对应的Content-Type消息头)。在这种情况下,浏览器会以针对这种数据类型(如果它识别该类型)的方式正常处理响应,因而通过HTML注入脚本代码的常用方法也可能会失效。

尽管难以实现,但在某些情况下我们仍然可以满足这两个条件,从而利用类似于XSS的行为来实施有效攻击。下面我们将举例说明如何使用XML数据格式来实施攻击。

√ 传送跨域XML请求

使用HTML表单(将enctype属性设置为text/plain)可以在HTTP请求主体中跨域传送几乎任何数据。这将告诉浏览器按以下方式处理表单参数:

在请求中隔行传送每个参数;

使用等号分隔每个参数的名称和值(和平常一样);

不对参数名称或值进行任何URL编码。

虽然某些浏览器并不遵循这种规范,但当前版本的Internet Explorer、Firefox和Opera都采用这种规范。

上述行为意味着,只要数据中至少包含一个等号,你就可以在消息主体中传送任意数据。为此,你需要将数据分隔成两块,等号前一块,等号后一块。然后,将第一块数据放在参数名称中,将第二块数据放在参数值中。这样,浏览器在构建请求时,它会传送以等号分隔的两块数据,因而实际上构建了所需的数据。

由于XML在起始XML标签的version属性中始终至少包含一个等号,因此,我们可以在消息主体中使用这种技巧跨域传送任意数据。例如,如果所需的XML如下所示:

则可以使用以下表单发送这些数据:

要在param参数的值中包含常用的攻击字符,如标签尖括号,你需要在XML请求中对这些字符进行HTML编码。因此,在生成该请求的HTML表单中,你需要对它们进行双重HTML编码。

 提示 只要你能够将等号合并到请求中的某个位置,就可以使用这种技巧跨域提交几乎包含任何类型的内容(如JSON编码的数据和序列化二进制对象)的请求。通常,通过修改请求中可以包含等号字符的自由格式的文本字段即可达到这一目的。例如,在下面的JSON数据中,注释字段被用于注入所需的等号字符:

在使用这种技巧时,唯一需要注意的地方是,生成的请求将包含以下消息头:

正常情况下,根据生成请求的具体方式,最初的请求本应包含一个不同的Content-Type消息头。如果应用程序接受提供的Content-Type消息头并正常处理消息主体,则在设计有效的XSS攻击时就可以使用这种技巧。如果由于Content-Type消息头已修改,应用程序无法正常处理请求,则可能没有办法跨域传送适当的请求来触发类似于XSS的行为。

 提示 如果在包含非标准内容的请求中确定了类似于XSS的行为,首先应该快速确定,在将Content-Type消息头更改为text/plain后,这种行为是否仍然存在。如果这种行为不再存在,则你不必付出任何其他努力来尝试设计有效的XSS攻击。

√ 从XML响应中执行JavaScript

在尝试利用非标准内容中的类似于XSS的行为时,你需要克服的第二个障碍是找到一种操纵响应的方法,使其在由浏览器直接处理时能够执行你的脚本。如果响应中包含错误的Content-Type消息头,或根本不包含Content-Type消息头,或者如果输入在响应主体的开始部分就已反射,则可以轻松克服这种障碍。

但是,响应通常都包含准确描述应用程序返回的数据类型的Content-Type消息头。此外,你的输入大多是在响应的中间部分反射。同时,在此位置之前和之后的响应内容包含的数据遵循指定内容类型的相关规范。不同浏览器解析内容的方式各不相同。一些浏览器完全信任Content-Type消息头,一些浏览器则会检查内容本身,并在具体的类型有所不同时覆盖指定的类型。但是,在这种情况下,无论浏览器如何处理内容,它都不大可能将响应作为HTML处理。

如果可以构建能够成功执行脚本的响应,这往往需要利用所注入的内容类型的特定语法特性。幸好,对于XML而言,你可以使用XML标记定义一个映射为XHTML的新命名空间,并使浏览器将该命名空间解析为HTML,从而达到执行脚本的目的。例如,在Firefox处理以下响应时,注入的脚本将得以执行:

如上所述,如果响应由浏览器直接处理,而不是由通常处理响应的原始应用程序组件处理时,此攻击将取得成功。

攻击浏览器XSS过滤器

在利用几乎任何反射型XSS漏洞时总是会遇到一个障碍,即各种浏览器功能都针对XSS攻击为用户提供了保护。Internet Explorer浏览器默认包含一个XSS过滤器,其他一些浏览器也通过插件的形式提供类似的功能。这些过滤器的工作方式基本类似:它们被动监视请求和响应,并使用各种规则来确定正在进行的潜在XSS攻击,一旦确定潜在攻击,就修改响应的某些部分来阻止这些攻击。

如前所述,如果可以通过任何广泛使用的浏览器来利用XSS条件,我们就应将这些条件视为漏洞。而且,某些浏览器提供XSS过滤器并不意味着不需要修复XSS漏洞。在某些现实情况下,攻击者可能恰恰需要通过包含XSS过滤器的浏览器来利用某个漏洞。此外,用于避开XSS过滤器的方法本身也值得我们关注。在某些情况下,我们甚至可以利用这些方法来实施通过别的方法无法实施的其他攻击。

在这一节中,我们将介绍Internet Explorer的XSS过滤器。目前,它是最成熟及应用最广泛的过滤器。

IE XSS过滤器的核心功能如下。

检查跨域请求中的每一个参数值,以确定注入JavaScript的可能尝试。它会根据一个常见攻击字符串的基于正则表达式的黑名单来检查这些值,从而完成这一任务。

如果发现潜在恶意的参数值,则检查响应,看其中是否包含相同的值。

如果响应中出现该值,则会对响应进行净化,以防止执行任何脚本。例如,它会将<script>修改为<sc#ipt>。

关于IE XSS过滤器,需要指出的第一个问题是:大体而言,它能够有效阻止利用XSS漏洞的标准攻击,从而为任何尝试实施这类攻击的攻击者设置了很大的障碍。这意味着,需要通过某些特定的方法才能避开这种过滤器。此外,还可以利用这种过滤器的工作机制来实施通过别的方法无法实施的其他攻击。

首先,利用这种过滤器的核心功能,我们可以找到一些避开该过滤器的方法。

该过滤器仅检查参数值,而不检查参数名称。一些应用程序易于受到针对参数名称的攻击,如在响应中回显请求的整个URL或整个查询字符串。该过滤器无法阻止这类攻击。

该过滤器单独检查每个参数值。但是,如果在同一个响应中反射多个参数,就可以将攻击从一个参数传递到另一个参数(如用于突破长度限制的技巧中所述)。如果可以将XSS有效载荷分割成几块,则其中任何一块都不会与受阻止的表达式黑名单相匹配,这样,过滤器将无法阻止攻击。

由于性能原因,该过滤器仅检查跨域请求。因此,如果攻击者能够使用户向XSS URL提出“本地”请求,过滤器将无法阻止这种攻击。通常,如果应用程序包含任何行为,允许攻击者在由其他用户查看的页面中注入任意链接,这种攻击即成为可能(虽然这本身也属于反射型攻击,但XSS过滤器仅尝试阻止注入的脚本,而不是注入的链接)。在这种情况下,攻击者需要完成两个步骤:在用户的页面中注入恶意链接;用户单击链接并收到XSS有效载荷。

其次,在某些情况下,可以利用浏览器和服务器的特殊行为来避开XSS过滤器。

如你所见,浏览器在处理HTML时接受各种类型的异常字符和语法。例如,IE本身就接受NULL字节。有时,攻击者可以利用IE的这种古怪行为来避开它的XSS过滤器。

如第10章所述,在请求包含多个同名参数时,应用程序服务器的行为各不相同。在某些情况下,它们会串联收到的所有值。例如,在ASP.NET中,如果查询字符串包含:

传递给应用程序的参数

pl的值为:

与之相反,即使参数的名称相同,但IE XSS过滤器仍然会单独处理每个参数。这种行为差异使得攻击者能够轻松在几个相同名称的“不同”请求参数之间传递XSS有效载荷,从而避开针对每个单独的值的黑名单过滤(因为服务器已将它们串联起来)。

尝试访问

当前,以下XSS攻击可成功避开IE XSS过滤器:

http://mdsec.net/error/5/Error.ashx?message=<scr%00ipt%20&message=> alert('xss')</script>

再次,通过利用该过滤器净化应用程序响应中的脚本代码的方式,可以实施通过其他方法无法实施的攻击。之所以会出现这种情况,主要是因为该过滤器以被动方式运行,仅寻找类似脚本的输入与类似脚本的输出之间的关联。它无法对应用程序进行交互性探查,以确定给定输入是否与给定输出相关联。因此,攻击者可以利用该过滤器净化在响应中出现的应用程序自身的脚本代码。如果攻击者在请求参数值中包含一部分现有脚本,IE XSS过滤器在请求和响应中发现相同的脚本代码,它就会修改响应中的脚本,以阻止该脚本执行。

事实证明,在某些情况下,净化现有脚本将改变包含用户输入反射的响应的后续部分的语法情境。这种情境的改变意味着应用程序自身对反射型输入的过滤不充分。因此,攻击者可以利用用户输入反射来实施XSS攻击(如果IE XSS过滤器没有修改响应,这种攻击将无法成功)。但是,能够实施这类攻击的情形通常与不常用的功能或早期版本的IE XSS过滤器中披露的缺陷(已修复)有关。

更重要的是,由于攻击者可以选择性地净化应用程序自身的脚本代码,因此,他们可以利用这种“能力”,通过破坏应用程序安全相关的控制机制来实施全然不同的攻击。一个常见的示例是删除防御性的framebusting代码(请参阅第13章了解详细信息),但其他大量示例主要与在客户端执行关键的安全防御任务的应用程序专用代码有关。

查找并利用保存型XSS漏洞

确定保存型XSS漏洞的过程与前面描述的确定反射型XSS漏洞的过程有很多相似之处,都包括在应用程序的每一个进入点提交一个特殊字符串。但是,这两个过程之间也存在一些重要的区别。在进行测试时,必须记住这些区别,以确定尽可能多的漏洞。

渗透测试步骤

(1)向应用程序中的每一个可能的位置提交一个特殊的字符串后,必须反复检查应用程序的全部内容与功能,确定这个字符串在浏览器中显示的任何情况。在某个位置(例如,个人信息页面的姓名字段)输入用户控制的数据,这个数据可能会在应用程序的许多不同位置显示(例如,用户主页上、注册用户列表中、任务等工作流程项目中、其他用户的联系列表中、用户提交的消息或问题中、应用程序日志中等)。应用程序可能对每个出现的字符串实施了不同的保护性过滤,因此需要对它们进行单独分析。

(2)如有可能,应检查管理员能够访问的所有应用程序区域,确定其中是否存在任何可被非管理用户控制的数据。例如,应用程序一般允许管理员在浏览器中检查日志文件。这种类型的功能极有可能包含XSS漏洞,攻击者通过生成含有恶意HTML的日志记录即可对其加以利用。

(3)在向应用程序中的每个位置提交一个测试字符串时,并不总是把它作为每个页面的每一个参数这样简单。在保存被提交的数据之前,许多应用程序功能需要经历几个阶段的操作。例如,注册新用户、处理购物订单、转账等操作往往需要按预定的顺序提交几个不同的请求。为避免遗漏任何漏洞,必须确保每次测试彻底完成。

(4)在探查反射型XSS漏洞时,应该注意可控的受害者请求的每一个方面。包括请求的所有参数和每一个HTTP消息头。在探查保存型XSS漏洞时,还应该分析应用程序用于接收并处理可控输入的任何带外通道。任何这类通道都是引入保存型XSS攻击的适当攻击向量。同时,审查在应用程序解析过程中得到的结果(请参阅第4章了解相关内容),确定每一个可能的受攻击面。

(5)如果应用程序允许文件上传与下载,应始终探查这种功能是否易于受到保存型XSS攻击。我们将在本章后面部分讨论测试这类功能的详细技巧。

(6)充分发挥想象,确定控制的数据是否可通过任何其他方法保存在应用程序中并显示给其他用户。例如,如果应用程序的搜索功能显示常用的搜索项列表,就可以通过多次搜索这个列表,引入保存型XSS有效载荷,即使主搜索功能本身安全地处理输入。

确定用户控制的数据被应用程序保存并随后在浏览器中显示的每一种情况后,应当遵循与前面描述的探查潜在的反射型XSS漏洞时相同的过程。也就是说,决定需要提交哪些输入,以在周围的HTML中嵌入有效的JavaScript,然后尝试避开干扰攻击有效载荷执行的过滤。

 提示 在探查反射型XSS漏洞时,每次测试一个参数,并检查每个响应中是否出现输入,就可以轻易确定哪些请求参数易于受到攻击。但是,在探查保存型XSS漏洞时,要确定这一点并不容易。如果在每个页面的每一个参数提交相同的测试字符串,那么你可能会发现,这个字符串在应用程序的许多位置重复出现,因而无法准确确定每个出现的字符串由哪个参数负责。为避免出现这个问题,在探查保存型XSS时,可以为每个参数提交一个不同的测试字符串,例如,把测试字符串与它提交到其中的字段名称连接起来。

在测试保存型XSS漏洞时,我们还可以采用一些特殊的技巧。在下面几节中,我们将详细介绍这些技巧。

在Web邮件应用程序中测试XSS

如前所述,由于Web邮件应用程序将直接从第三方收到的内容包含在向用户显示的应用程序页面中,因此,这种程序本身就存在着保存型XSS攻击风险。要测试这种功能,应该在该应用程序上创建自己的电子邮件账户,并通过电子邮件向自己实施大量XSS攻击,然后在该应用程序中查看每封邮件,确定是否有任何攻击取得成功。

为彻底完成这一任务,你需要通过电子邮件发送各种反常的HTML内容(如我们在测试避开输入过滤的方法时所述)。如果仅限于使用标准电子邮件客户端,你可能会发现,无法完全控制原始的邮件内容,或者邮件客户端可能会净化或“清除”你有意设计的畸形语法。

在这种情况下,最好是采用其他方法来生成电子邮件,以便于直接控制邮件的内容。一种方法是使用UNIX sendmail命令。首先,需要使用应当用于向外发送电子邮件的邮件服务器的详细信息配置电脑;然后,可以在文本编辑器中创建原始的电子邮件,并使用以下命令发送该邮件:

以下为原始电子邮件文件的一个示例。在消息主体中测试各种XSS有效载荷和避开过滤的机制时,也可以尝试指定不同的Content-Type和charset:

在上传文件中测试XSS

如果应用程序允许用户上传可被其他用户下载并查看的文件,就会出现保存型XSS漏洞;然而,这种漏洞常常被人们忽略。如今的应用程序通常都提供文件上传功能,除传统的用于文件共享的工作流功能外,文件还可以通过电子邮件附件的形式传送给Web邮件用户。图像文件则可以附加到博客文章中,并且可以用作定制的头像或通过相册共享。

应用程序是否易于受到上传文件的攻击,取决于许多影响因素:

在文件上传过程中,应用程序可能会限制可以上传的文件的扩展名。

在文件上传过程中,应用程序可能会检查文件内容,以确认其是否为所需的格式,如JPEG。

在文件下载过程中,应用程序可能会返回Content-Type消息头,以指定文件所包含的内容的类型,如image/jpeg。

在文件下载过程中,应用程序可能会返回Content-Disposition消息头,以指定浏览器应将文件保存到磁盘上。否则,对于相关的内容类型,应用程序会处理并在用户的浏览器中显示文件。

在测试文件上传功能时,首先你应该尝试上传一个包含概念验证脚本的简单HTML文件。如果该文件被接受,则尝试以正常方式下载该文件。如果应用程序按原样返回最初的文件,并且你的脚本得以执行,则应用程序肯定易于受到攻击。

如果应用程序阻止上传的文件,则尝试使用各种文件扩展名,包括.txt和.jpg。如果在你使用其他扩展名时,应用程序接受包含HTML的文件,则应用程序可能仍然易于受到攻击,具体取决于其在下载过程中如何传送文件。Web邮件应用程序通常易于受到这类攻击。攻击者可以发送包含诱惑性图像附件的电子邮件,如果用户查看该附件,他们的会话将被攻破。

即使应用程序返回Content-Type消息头,指定下载文件应为图像,但是,如果文件实际包含的是HTML内容,一些浏览器仍然会将该文件作为HTML处理。例如:

旧版的Internet Explorer就存在这种缺陷。如果用户直接请求一个JPEG文件(并非通过嵌入式<img>标签),那么在收到上述响应时,IE会将该文件的内容当做HTML处理。虽然这种缺陷已经得到修复,但将来在其他浏览器中也可能会出现此类缺陷。

混合文件攻击

通常,为防范上述攻击,应用程序会对上传文件的内容执行某种确认,以确保其确实包含所需格式的数据,如图像。但是,使用“混合文件”(在一个文件中组合两种不同的格式)仍然可以对这些应用程序实施攻击。

Billy Rios设计的GIFAR文件就是一种常见的混合文件。GIFAR文件包含GIF图像格式和JAR(Java档案)格式的数据,并且是这两种格式的有效实例。这是因为,与GIF格式相关的文件元数据位于文件的开始部分,与JAR格式相关的元数据则位于文件的结尾部分。因此,如果应用程序允许包含GIF数据的文件,那么,在确认上传文件的内容时,该应用程序也会接受GIFAR文件。

通常,使用GIFAR文件实施的上传文件攻击由以下步骤组成。

攻击者发现由一名用户上传的GIF文件可由其他用户下载(如社交网络应用程序中的用户头像)的应用程序功能。

攻击者构建一个GIFAR文件,在其中包含一段Java代码,用于劫持任何执行该代码的用户的会话。

攻击者将该文件作为他的头像上传。因为其中包含有效的GIF图像,应用程序将接受该图像。

攻击者确定可利用上传的文件对其实施攻击的适当外部网站。该网站可能为攻击者自己的网站,或允许用户创建任意HTML(如博客)的第三方站点。

在该外部网站上,攻击者使用<applet>和<object>标签从上述社交网络站点以Java applet的形式上传GIFAR文件。

如果用户访问该外部站点,攻击者的Java applet将在其浏览器中执行。与包含正常脚本的文件不同,在遇到Java applet时,同源策略的执行方式会有所不同。Java applet将被视为属于加载它的域,而不是调用它的域。因此,攻击者的applet将在社交网络应用程序的域中执行。如果受害用户在受到攻击时已登录该社交网络应用程序,或者最近曾登录该应用程序并选中了 “保持登录状态”(stay logged in)选项,则攻击者的applet将完全控制受害用户的会话,从而侵入该用户。

当前版本的Java浏览器插件通过确认所加载的JAR文件是否包含混合内容,从而阻止了这种使用GIFAR文件的特殊攻击。但是,使用混合文件隐藏可执行文件的原理仍然适用。鉴于当前所使用的客户端可执行代码格式的范围不断扩大,攻击者或许可以以其他格式,或在将来通过其他方式实施类似的攻击。

在通过Ajax上传的文件中测试XSS

一些应用程序使用Ajax来检索和呈现在片段标识符之后指定的URL。例如,应用程序的页面中可能包含以下链接:

http://wahh-app.com/#profile

当用户单击此链接时,客户端脚本将处理单击事件,使用Ajax来检索在片段标识符之后显示的文件,并在现有页面中的<div>元素的innerHtml中设置响应。这样可提供无缝的用户体验,因为单击用户界面中的选项卡将更新所显示的内容,而无须重新加载整个页面。

在这种情况下,如果应用程序还包含其他允许上传和下载图像文件(如用户头像)的功能,你就可以上传一个包含嵌入式HTML标记的有效图像文件,并构建以下URL,使客户端代码提取该图像并将其作为HTML显示:

http://wahh-app.com/#profiles/images/15234917624.jpg

HTML可以嵌入到有效图像文件的各种位置,包括图像的注释部分。一些浏览器,包括Firefox和Safari,乐于将图像文件以HTML格式显示。图像的二进制部分将显示为乱码,而任何嵌入的HTML将正常显示。

 提示 假设潜在的受害者使用的是兼容HTML5的浏览器,如果所请求的域许可,该浏览器可用于跨域传送Ajax请示。在这种情况下,另一种可能的攻击方法,是在片段标识符后面放置一个绝对URL,指定一个位于可与目标域进行Ajax交互的服务器上的、完全由攻击者控制的外部HTML文件。如果客户端脚本不确认所请示的URL是否在同一个域上,客户端远程文件包含攻击将取得成功。

由于旧版HTML并不需要对URL的域进行此类确认,因此,HTML5中对域确认所做的更改可能会给此前安全的应用程序造成可利用的漏洞。

查找并利用基于DOM的XSS漏洞

使用以下方法无法确定基于DOM的XSS漏洞:提交一个特殊的字符串作为每个参数,然后监控响应中是否出现该字符串。

确定基于DOM的XSS漏洞的基本方法是,用浏览器手动浏览应用程序,并修改每一个URL参数,在其中插入一个标准测试字符串,例如:

通过在浏览器中显示每一个返回的页面,可以执行所有客户端脚本,并在必要时引用经过修改的URL参数。只要包含cookie的对话框出现,就表示发现了一个漏洞(可能为基于DOM的或其他类型的XSS漏洞)。使用本身提供JavaScript解释器的工具甚至可以自动完成这个过程。

然而,这种基本的方法并不能确定所有基于DOM的XSS漏洞。如上文所述,在HTML文档中注入有效JavaScript所需的准确语法,取决于用户可控制的字符串插入点前后已经存在的语法。这时,可能需要终止被单引号或双引号引用的字符串,或者结束特定的标签。有时可能需要插入新标签,但有时并不需要。客户端应用程序代码可能会尝试确认通过DOM获得的数据,但它仍然易于受到攻击。

即使应用程序可能易于受到精心设计的攻击,但如果插入标准测试字符串仍不能得到有效的语法,那么嵌入式JavaScript将不会执行,因此也不会有对话框出现。由于无法在每个参数中提交每一种可能的XSS攻击字符串,这种基本的探查方法必然会遗漏大量的漏洞。

确定基于DOM的XSS漏洞的一种更加有效的方法,是检查所有客户端JavaScript,看其中是否使用了任何可能会导致漏洞的DOM属性。有大量工具可用于自动完成这个测试过程。其中一个有用的工具为DOMTracer,下载该工具的URL如下所示:

www.blueinfy.com/tools.html

渗透测试步骤

使用在应用程序解析过程中得到的结果(请参阅第4章了解相关内容),检查每一段客户端JavaScript,看其中是否出现以下API,它们可用于访问通过一个专门设计的URL控制的DOM数据:

document.1ocation

document.URL

document.URLUnencoded

document.referrer

window.location

确保检查出现在静态HTML页面及动态生成的页面中的脚本,无论页面为何种类型,或者是否有参数被提交给页面,任何使用脚本的位置都可能存在基于DOM的XSS漏洞。

在每一个使用上述API的位置,仔细检查那里的代码,确定应用程序如何处理用户可控制的数据,以及是否可以使用专门设计的输入来执行任意JavaScript。尤其注意检查并测试控制的数据被传送至以下任何一个API的情况:

document.write()

document.writeln()

document.body.innerHtml

eval()

window.execScript()

window.setInterval()

window.setTimeout()

尝试访问

http://mdsec.net/error/18/

http://mdsec.net/error/22/

http://mdsec.net/error/28/

http://mdsec.net/error/31/

http://mdsec.net/error/37/

http://mdsec.net/error/41/

http://mdsec.net/error/49/

http://mdsec.net/error/53/

http://mdsec.net/error/56/

http://mdsec.net/error/61/

和查找反射与保存型XSS漏洞时一样,应用程序可能会执行各种过滤,尝试阻止相关攻击。通常,这些过滤应用于客户端,因此,可以直接查看其确认代码,以了解其工作机制,并尝试确定任何避开过滤的方法。上文介绍的用于避开针对反射型XSS攻击的过滤技巧在此处同样适用。

尝试访问

http://mdsec.net/error/92/

http://mdsec.net/error/95/

http://mdsec.net/error/107/

http://mdsec.net/error/109/

http://mdsec.net/error/118/

某些情况下,服务器端应用程序可能会实施旨在阻止基于DOM的XSS攻击的过滤。即使客户端出现易受攻击的操作,服务器并不在响应中返回用户提交的数据,但是URL仍然被提交给了服务器;因此,当应用程序检测到恶意有效载荷时,它会对数据进行确认,且不会返回易受攻击的客户端脚本。

如果遇到这种防御,渗透测试员应该尝试使用前面在查找反射型XSS漏洞时描述的每一种可能避开过滤的攻击方法,测试服务器数据确认机制的可靠性。除这些攻击外,还有几种专门针对基于DOM的XSS漏洞的技巧可用于帮助攻击有效载荷避开服务器端确认。

当客户端脚本从URL中提取参数值时,它们很少将查询字符串正确解析成名/值对。相反,它们通常会在URL中搜索后面紧跟着等号(=)的参数名称,然后提取出等号以后直到URL结束位置的内容。这种行为能够以两种方式加以利用:

如果服务器根据每个参数而不是整个URL应用确认机制,那么可以将有效载荷插入到附加在易受攻击的参数后面的一个虚构的参数中。例如:

这时,虚构的参数被服务器忽略,因此不会受到任何过滤。但是,因为客户端脚本在查询字符串中搜索message=,并提取其后的全部内容,所以它处理的字符串中正好包含该有效载荷。

如果服务器对整个URL而不仅仅是消息参数应用确认机制,仍然可以将有效载荷插入到HTML片断字符#的右边,从而避开过滤。例如:

这时,片断字符串仍然属于URL的一部分,因此被保存在DOM中,并由易受攻击的客户端脚本处理。但是,由于浏览器并不将URL中的片断部分提交给服务器,因此攻击字符串不会传送到服务器中,因而不会被任何服务器端过滤所阻止。因为客户端脚本提取message=后面的全部内容,所以有效载荷仍然被复制到HTML页面源代码中。

尝试访问

http://mdsec.net/error/76/

http://mdsec.net/error/82/

 错误观点 “我们检查每个用户请求中是否存在嵌入式脚本标签,因此不可能受到XSS攻击。”

除避开过滤是否可行外,现在可以找到3个原因证明这种看法并不正确。

在一些XSS漏洞中,攻击者控制的数据被直接插入到现有的JavaScript脚本中,因此攻击者不需要使用任何脚本标签,或采用其他方法引入脚本代码。在其他情况下,仍然不需要使用任何脚本标签,只需注入一个包含JavaScript的事件处理器即可。

如果应用程序接受通过带外通道传送的数据,并在它的Web界面中显示这些数据,那么攻击者不用使用HTTP提交任何恶意有效载荷,就可以利用任何保存型XSS漏洞。

针对基于DOM的XSS漏洞的攻击不需要向服务器提交任何恶意有效载荷。如果使用片断技巧,那么有效载荷将始终位于客户端。

一些应用程序使用更加复杂的客户端脚本,对查询字符串进行更加严格的解析。例如,它在URL中搜索后面紧跟着等号的参数名称,然后提取等号后面的内容,直到遇到一个分隔符,如&或#。在这种情况下,可以对前面的两个攻击进行如下修改:

在这两个示例中,第一个message=后面紧跟着攻击字符串,其中没有任何干扰脚本执行的分隔符,因此攻击有效载荷将得到处理,且被复制到HTML页面源代码中。

尝试访问

http://mdsec.net/error/79/

有时候,基于DOM的数据经过了非常复杂的处理,仅通过静态审查JavaScript源代码可能很难追踪用户控制的数据采用的不同路径以及对它进行的各种操作。在这种情况下,使用JavaScript调试器动态监控脚本的执行情况可能会有很大帮助。Firefox浏览器的FireBug扩展是一款用于监控客户端代码与内容的优秀调试器,可用于设置断点、监视感兴趣的代码与数据,为我们了解复杂脚本的执行过程提供了极大的便利。

 错误观点 “我们很安全,因为Web应用程序扫描器没有发现任何XSS漏洞。”

第19章将会介绍,一些Web应用程序扫描器能够发现大多数常见的漏洞,包括XSS漏洞。但是,很显然,许多XSS漏洞很难检测出来,发现它们可能需要进行大量探查与试验。就目前而言,还没有任何自动工具能够准确确定所有这些漏洞。

防止XSS攻击

尽管XSS的表现形式各异,利用方法各不相同,但从概念上讲,防止这种漏洞实际上相当简单。预防它们之所以存在困难,主要在于我们无法确定用户可控制的数据以潜在危险的方式被处理的每一种情况。任何应用程序页面都会处理并显示一些用户数据。除核心功能外,错误消息与其他位置也可能产生漏洞。因此,XSS漏洞普遍存在也就不足为奇了,即使在最为注重安全的应用程序中也是如此。

由于造成漏洞的原因各不相同,一部分防御方法适用于反射型与保存型XSS漏洞,而另一些则适用于基于DOM的XSS漏洞。

防止反射型与保存型XSS漏洞

用户可控制的数据未经适当确认与净化就被复制到应用程序响应中,这是造成反射型与保存型XSS漏洞的根本原因。由于数据被插入到一个HTML页面的源代码中,恶意数据就会干扰这个页面,不仅修改它的内容,还会破坏它的结构(影响引用字符串、起始与结束标签、注入脚本等)。

为消除反射型与保存型XSS漏洞,首先必须确定应用程序中用户可控制的数据被复制到响应中的每一种情形。这包括从当前请求中复制的数据以及用户之前输入的保存在应用程序中的数据,还有通过带外通道输入的数据。为确保确定每一种情形,除仔细审查应用程序的全部源代码外,没有其他更好的办法。

确定所有可能存在XSS风险、需要适当进行防御的操作后,需要采取一种三重防御方法阻止漏洞的发生。这种方法由以下3个因素组成:

确认输入;

确认输出;

消除危险的插入点。

如果应用程序需要允许用户以HTML格式创建内容(如允许在注释中使用HTML的博客应用程序),这时应谨慎使用这种方法。我们将在介绍常规防御技巧后讨论与这种情况有关的一些特定注意事项。

确认输入

如果应用程序在某个位置收到的用户提交的数据将来有可能被复制到它的响应中,应用程序应根据这种情形对这些数据执行尽可能严格的确认。可能需要确认的数据的特性包括以下几点。

数据不是太长。

数据仅包含某组合法字符。

数据与一个特殊的正规表达式相匹配。

根据应用程序希望在每个字段中收到的数据类型,应尽可能限制性地对姓名、电子邮件地址、账号等应用不同的确认规则。

确认输出

如果应用程序将某位用户或第三方提交的数据复制到它的响应中,那么应用程序应对这些数据进行HTML编码,以净化可能的恶意字符。HTML编码指用对应的HTML实体替代字面量字符。这样做可确保浏览器安全处理可能为恶意的字符,把它们当做HTML文档的内容而非结构处理。一些经常造成问题的字符的HTML编码如下:

"——"

'——'

&——&

<—&md&;<

>——>

除这些常用的编码外,实际上,任何字符都可以用它的数字ASCII字符代码进行HTML编码,举例如下:

%——%

*——*

应该注意,在将用户输入插入到标签属性值中时,浏览器会首先对该值进行HTML解码,然后执行其他处理。在这种情况下,仅仅对任何在正常情况下存在问题的字符进行HTML编码的防御机制可能会失效。确实,如前所述,对于某些过滤,攻击者可以避免在有效载荷中使用HTML编码的字符。例如:

我们在下一节将会讲到,最好是避免在这些位置插入用户可控制的数据。如果在某些情况必须这样做,在执行操作时应特别小心,以防止任何可以避免过滤的情况。例如,如果将用户输入插入到事件处理器中的引用JavaScript字符串中,应使用反斜线正确转义用户输入中的任何引号或反斜线,并且HTML编码应包括&和;字符,以防止攻击者自己执行HTML编码。

在将用户可控制的字符串复制到服务器的响应中之前,ASP.NET应用程序可以使用Server.HTMLEncode API净化其中的常见恶意字符。这个API把字符"&<和>转换成它们对应的HTML实体,并且使用数字形式的编码转换任何大于0x7f的ASCII字符。

在Java平台中没有与之等效的API;但是可以使用数据形式的编码构造自己的等效方法。例如:

当处理用户提交的数据时,开发者常常会犯一个错误,即仅对在特殊情况下对攻击者有用的字符进行HTML编码。例如,如果数据被插入到一个双引号引用的字符串中,应用程序可能只编码"字符;如果数据被插入到一个没有引号的标签中,应用程序只会编码>字符。这种方法明显增加了攻击者避开确认的风险。攻击者常常利用浏览器接受无效HTML与JavaScript的弱点,改变确认情境或以意外的方式注入代码。而且,攻击者可以将一个攻击字符串分布到几个可控制的字段中,利用应用程序对每个字段采用的不同过滤避开其他过滤。一种更加可靠的方法是,无论数据插入到什么地方,始终对攻击者可能使用的每一个字符进行HTML编码。为尽可能地确保安全,开发者可能会选择HTML编码每一个非字母数字字符,包括空白符。这种方法通常会显著增加应用程序的工作压力,同时给任何尝试避开过滤的攻击设置巨大障碍。

应用程序之所以结合使用输入确认与输出净化,原因在于这种方法能够提供两层防御:如果其中一层被攻破,另一层还能提供一些保护。如上文所述,许多执行输入与输出确认的过滤都容易被攻破。结合这两种技巧,应用程序就能够获得额外的保护,即使攻击者发现其中一种过滤存在缺陷,另一种过滤仍然能够阻止他实施攻击。在这两种防御中,输出确认更为重要,必不可少。实施严格的输入确认应被视为一种次要故障恢复(secondary failover)。

当然,当设计输入与输出确认机制时,我们应特别小心,尽量避免任何可能导致攻击者避开确认的漏洞。尤其要注意的是,应在实施相关规范化后再对数据进行过滤与编码,而且之后不得对数据实施进一步的规范化。应用程序还必须保证其中存在的空字节不会对它的确认造成任何干扰。

消除危险的插入点

应用程序页面中有一些位置,在这里插入用户提交的输入就会造成极大的风险;因此,开发者应力求寻找其他方法执行必要的功能。

应尽量避免直接在现有的JavaScript中插入用户可控制的数据。这适用于<script>标签中的代码,也适用于事件处理器的代码。如果应用程序尝试以安全的方式在其中插入数据,可能就会使攻击者有机会避开它们实施的防御性过滤。一旦攻击者能够控制提交数据的插入点,他不用付出多大努力就可以注入任意脚本命令,从而实施恶意操作。

如果标签属性接受URL作为它的值,通常应用程序应该避免嵌入用户输入,因为各种技巧也能引入脚本代码,包括伪协议脚本处理的使用。

如果攻击者通过插入一个相关指令,或者因为应用程序使用一个请求参数指定首选的字符集,因而能够控制应用程序响应的编码类型,那么这些情况也应该加以避免。在这种情况下,在其他方面经过精心设计的输入与输出过滤可能就会失效,因为攻击者的输入进行了不常见的编码,以致上述过滤并不将其视为恶意输入。只要有可能,应用程序应在它的响应消息头中明确指定一种编码类型,禁止对它进行任何形式的修改,并确保应用程序的XSS过滤与其兼容。例如:

允许有限的HTML

一些应用程序需要允许用户以HTML格式提交即将插入到应用程序响应中的数据。例如,博客应用程序可能需要允许用户使用HTML撰写博客、对博客使用格式、嵌入链接或图像等。在这种情况下,不作区分地应用上述措施将会导致错误。用户的HTML标记将在响应中被HTML编码,因此作为真实的标记显示在屏幕上,而不是以所需的格式化内容显示。

为安全地支持这种功能,应用程序需要保持稳健,仅允许有限的HTML子集,避免提供任何引入脚本代码的方法。这包括采用一种白名单方法,仅允许特定的标签和属性。成功做到这一点并不简单,如前所述,攻击者可以通过各种方法使用看似无害的标签来执行代码。

例如,如果应用程序允许使用<b>和<i>标签,但并不限制与这些标签一起使用的属性,则攻击者可以实施以下攻击:

此外,如果应用程序允许使用看似安全的<a>标签和href属性的组合,则攻击者可以实施以下攻击:

有各种框架(如OWASP AntiSamy项目)可用于确认用户提交的HTML标记,以确保其中不包含任何执行JavaScript的方法。建议需要允许用户创建有限HTML的开发者直接使用某个成熟的框架,或仔细分析其中一种框架,以了解面临的各种相关挑战。

或者,也可以采用某种定制的中间标记语言,允许用户使用有限的中间语言语法,然后由应用程序对其进行处理,以生成相应的HTML标记。

防止基于DOM的XSS漏洞

很明显,迄今为止,我们描述的防御机制并不能防止基于DOM的XSS漏洞,因为造成这种漏洞并不需要将用户可控制的数据复制到服务器响应中。

应用程序应尽量避免使用客户端脚本处理DOM数据并把它插入到页面中。由于被处理的数据不在服务器的直接控制范围内,有时甚至不在它的可见范围内,因此这种行为存在着固有的风险。

如果无法避免地要以这种方式使用客户端脚本,我们可以通过两种防御方法防止基于DOM的XSS漏洞,它们分别与前面描述的防止反射型XSS漏洞时使用的输入与输出确认相对应。

确认输入

许多时候,应用程序可以对它处理的数据执行严格的确认。确实,在这方面,客户端确认比服务器端确认更加有效。在前面描述的易受攻击的示例中,我们可以通过确认将要插入到文档中的数据仅包含字母数字字符与空白符,从而阻止攻击发生。例如:

除这种客户端控制外,还可以在服务器端对URL数据进行严格的确认,实施深层防御,以检测利用基于DOM的XSS漏洞的恶意请求。在刚刚说明的同一个示例中,应用程序甚至只需实施服务器端数据确认,通过确认以下数据来阻止攻击:

查询字符串中只有一个参数;

参数名为message(大小写检查);

参数值仅包含字母数字内容。

实施了这些控制后,客户端脚本仍有必要正确解析出message参数的值,确保其中并不包含任何URL片断字符。

确认输出

与防止反射型XSS漏洞时一样,在将用户可控制的DOM数据插入到文档之前,应用程序也可以对它们进行HTML编码。这样就可以将各种危险的字符与表达式以安全的方式显示在页面中。例如,使用下面的函数即可在客户端JavaScript中执行HTML编码:

小结

在这一章中,我们讨论了各种可能导致XSS漏洞的情形,以及一些可用于避开基于过滤的常用防御机制的方法。由于XSS漏洞极为常见,因此,测试员能够轻易在应用程序中发现可供利用的漏洞。但是,如果实施的各种防御机制迫使测试员设计出高度自定义的输入,或者利用HTML、JavaScript或VBScript的某些鲜为人知的特性来实施成功的攻击,这时,XSS将会更加引人关注(至少从研究角度看的确如此)。

下一章将以此为基础,并进一步讨论大量导致用户遭受恶意攻击的服务器端Web应用程序漏洞。

问题

欲知问题答案,请访问http://mdsec.net/wahh。

(1)在应用程序的行为中,有什么“明显特征”可用于确定大多数XSS漏洞?

(2)假设在应用程序未通过验证的功能区域发现了一个反射型XSS漏洞。如何利用这个漏洞攻破一个通过验证的应用程序会话?请想出两种不同的方法。

(3)假设一个cookie参数未经过任何过滤或净化就被复制到应用程序的响应中。是否可以利用这种行为在返回的页面中注入任意JavaScript?是否可以利用这种行为实施攻击其他用户的XSS攻击?

(4)假设在仅返回给自己的数据中发现了保存型XSS漏洞。这种行为是否存在安全缺陷?

(5)在一个处理文件附件并在浏览器中显示这些内容的Web邮件应用程序中,可以立即确定哪种常见的漏洞?

(6)浏览器的同源策略如何给Ajax技术XMLHttpRequest的应用造成影响?

(7)列举3个利用XSS漏洞的可行攻击有效载荷(也就是说,攻击者可以在其他用户的浏览器中执行的恶意操作而不是传送攻击的方法)。

(8)已知一个反射型XSS漏洞,可以在返回页面的HTML代码的某个位置注入任意数据。插入的数据被截短至50字节,但是我们希望注入一个超长的脚本,并且不想调用外部服务器上的脚本。如何解决长度限制呢?

(9)在一个必须使用POST方法的请求中发现一个反射型XSS漏洞。攻击者可以使用哪种传送机制实施攻击?

浙ICP备11005866号-12