注入XPath
XPath(XML路径语言)是一种利用与导航XML文档并从中获取数据的解释型语言。许多时候一个XPath表达式代表由一个文档节点导航到另一个文档节点所需要的一系列步骤。
如果Web应用程序将数据保存在XML文档中,那么它们可能使用XPath访问数据,以响应用户提交的输入。如果这个输入未经任何过滤或净化就插入到XPath查询中,攻击者就可以通过控制查询来破坏应用程序的逻辑,或者获取未授权访问的数据。
通常,XML文档并不是保存企业数据的首选工具。但是,它们常常被用于保存可根据用户输入获取的应用程序配置数据。小型应用程序也可使用它们保存简单的信息,如用户证书、角色和权限。以下面的XML数据为例:
1 | <addressBook> |
一个获取所有电子邮件地址的XPath查询如下:
//address/email/text()
一个返回Dawes的全部用户资料的查询为:
//address[surname/text() = ‘Dawes’]
在一些应用程序中,用户提交的数据可被直接嵌入到XPath查询中,查询的结果可能在应用程序的响应中返回,或者用于决定应用程序某些方面的行为。
破坏应用程序逻辑
以一个根据用户名和密码获得用户保存的信用卡号码的应用程序功能为例。下面的XPath查询核实用户提交的整数,并获取相关用户的信用卡号码:
//address[surname/text()=’Dawes’ and password/text() = ‘secret’]/ccard/text()
与利用SQL注入漏洞一样,这时攻击者也可以破坏应用程序的查询。例如,提交密码值
‘ or ‘a’=’a
将导致下面的XPath查询,获取所有用户的信用卡信息:
//address[surname/text() = ‘Dawes’ and password/text() = ‘’ or ‘a’ = ‘a’]/ccard/text()
另外,与SQL注入一样,注入一个数字值时不需要单引号。但与SQL查询不同,XPath查询中的关键字区分大小写,XML文档中的元素名也区分大小写。
谨慎XPath注入
攻击者可利用Xpath注入漏洞从目标XML文档中获取任意信息。获取信息的一种可靠途径是使用和上述SQL注入时相同的技巧,促使应用程序根据攻击者指定的条件以不同的方式作出响应。
提交以下两个密码将导致应用程序的不同行为:第一种情况返回结果,但第二种情况不返回结果。
‘ or 1 = 1 and ‘a’ = ‘a
‘ or 1 = 2 and ‘a’ = ‘a
这种行为差异可用于测试任何特殊条件的真假,因此可通过它依次第一个字节地提取出任意信息。与SQL一样,XPath语言也包含一个子字符串函数,可用它依次一个字符地测试一个字符串的值。例如,提交密码
‘ or //address[surname/text()=’Gates’ and substring(password/text(), 1, 1) = ‘M’] and ‘a’ = ‘a
将导致下面的XPath查询,如果用户Gates密码的第一个字符为M,将返回查询结果:
//address[surname/text()=’Dawes’ and password/text() = ‘’ or //address[surname/text() = ‘Gates’ and substring(password/text(), 1, 1) = ‘M’] and ‘a’ = ‘a ‘]/ccard/text()
轮流针对每个字符为止并测试每个可能的值,攻击者就能够获得Gate的完整密码。
盲目XPath注入
在前面的攻击中,注入的测试条件指定了提取数据的绝对路径以及目标字段的名称。实际上,即使不了解这些信息,攻击者仍有可能发动完全盲目的攻击。Xpath查询可包括与XML文档中当前节点的有关步骤,因此,从当前节点可以导航到父节点或一个特定的子节点。另外,XPath包含可查询文档原信息(包含特殊元素的名称)的函数。使用这些技巧就可以提取出文档中所有节点的名称与之,而不必提前知道与它的结构或内容有关的任何信息。
例如,可以使用前面描述的子字符串技巧,通过提交如下格式的密码,提取当前节点的父节点名称:
‘ or substring(name(parent::*[position() = 1]), 1, 1) = ‘a
这个输入能够返回结果,因为address节点的第一个字母为a。轮到第二个字母,这时可以提交下列密码确定该字母为d,因为最后一个输入返回了结果:
‘ or substring(name(parent::*[position()=1]),2,1) = ‘a
‘ or substring(name(parent::*[position()=1]),2,1) = ‘b
‘ or substring(name(parent::*[position()=1]),2,1) = ‘c
‘ or substring(name(parent::*[position()=1]),2,1) = ‘d
确定address节点的名称后,攻击者就可以轮流攻击他的每一个子节点,提取出它们的名称与值。通过索引指定相关子节点可不必知道任何节点的名称。例如,下面的查询将返回值Hunter:
//address[position() = 3]/child::node()[position() = 4]/text()*
而下面的查询返回值letmein:
//address[position() = 3]/child::node()[position() = 6]/text()*
这种技巧可用在完全盲目的攻击中,这时应用程序在响应中不返回任何结果,我们可以设计一个注入的条件,通过索引指定目标节点。例如,如果Gates密码的第一个字母为M,提交下面的密码将返回结果:
‘ or substring (//address[position()=1]/child::node()[position() = 6]text(), 1, 1) = ‘M’ and ‘a’ = ‘a
轮流攻击每个地址节点的每个子节点,并以此一个字符地提取出它们的值,攻击者就可以提取整个XML数据的内容。
查找XPath注入漏洞
许多常用于探查SQL注入漏洞的攻击字符串如果被提交给一个易于受到XPath注入的函数,往往会导致反常行为。例如,下面的两个字符会破坏XPath查询的语法,从而造成错误:
‘
‘–
通常,与在SQL注入漏洞中一样,下面的一个或几个字符串将会引起应用程序的行为发生变化,但不会造成错误:
‘ or ‘a’ = ‘a
‘ and ‘ a’ = ‘b
or 1 = 1
and 1=2
因此,任何时候,如果在探查SQL注入过程中发现一个漏洞的初步证据,但却无法对该漏洞加以利用,那么遇到的可能就是XPath注入漏洞。
防止XPath注入
如果觉得必须在一个XPath查询中插入用户提交的输入,应该只提交可事实严格输入确认的简单数据。应根据一份可接受字符组成的“白名单”检查用户输入,其中最好只包括字母数字字符。应阻止任何可能破坏XPath查询的字符,包括( ) = ‘ [ ] : , * / 和所有空白符。直接拒绝而不是净化任何与白名单不匹配的输入。