读他人知识获得的一些知识

首先是一些JAVA审计中很重要的知识,其与JAVA的基础知识关系不大。

RMI的调用流程、RMI注册表以及动态加载类

RMI的调用流程,从我之前写过的 真实环境下的FastJson1.2.47反序列化 参考,首先POST到服务器一串Payload

该Payload有我们的恶意class的服务器地址和恶意路径。

受害者服务器访问该链接去调取恶意class。并执行。

其中Exploit.class由Exploit.java生成如图所示:

1575430860323

而这里的Exploit.class即为远程对象

而远程方法调用则是技术,其中可选用rmi和ldap

1.2 远程对象

使用远程方法调用,必然会涉及参数的传递和执行结果的返回。参数或者返回值可以是基本数据类型,当然也有可能是对象的引用。所以这些需要被传输的对象必须可以被序列化(实现java.io.Serializable 接口),并且客户端的serialVersionUID字段要与服务器端保持一致。

1
2
3
4
serialVersionUID:
serialVersionUID适用于java序列化机制。
简单来说,JAVA序列化的机制是通过判断类的serialVersionUID来验证的版本一致的。
在进行反序列化时,JVM会把传来的字节流中的serialVersionUID于本地相应实体类的serialVersionUID进行比较。如果相同说明是一致的,可以进行反序列化,否则会出现反序列化版本一致的异常,即是InvalidCastException。

对RMI而言:任何可以被远程调用方法的对象需要实现下列三种情形的一种

  1. 实现 java.rmi.Remote 接口
  2. 远程对象的实现类必须继承UnicastRemoteObject类。
  3. 手工初始化远程对象,并在远程对象的构造方法的调用UnicastRemoteObject.exportObject()静态方法。

如下是三种情形示范:

第一种实现 java.rmi.Remote 接口

1
2
3
4
5
6
import java.rmi.Remote;
public class HelloRmi implements Remote {
public static void main(String[] args) {
// write your code here
}
}

第二种远程对象的实现类必须继承UnicastRemoteObject类

1
2
3
4
5
6
7
public class Main extends UnicastRemoteObject {
protected Main() throws RemoteException {
}
public static void main(String[] args) {
// write your code here
}
}

第三种手工初始化远程对象,并在远程对象的构造方法的调用UnicastRemoteObject.exportObject()静态方法

1
2
3
4
5
6
7
8
9
10
11
public class HelloImpl implements IHello {
protected HelloImpl() throws RemoteException {
UnicastRemoteObject.exportObject(this, 0);//此处为远程对象的构造方法
}

@Override
public String sayHello(String name) {
System.out.println(name);
return name;
}
}

在JVM之间通信时,RMI对远程对象和非远程对象的处理方式不一样, 它并没有直接把远程对象复制一份传递给客户端,而是传递了**一个远程对象的Stub **。

Stub:

Stub基本上相当于是远程对象的引用或者代理。Stub对开发者是透明的,客户端可以像调用本地方法一样直接通过它来调用远程方法。

Stub中包含了远程对象的定位信息,如Socket端口、服务端主机地址等等,并实现了远程调用过程中具体的底层网络通信细节

1575432491296

因此,Stub对于RMI通信至关重要,前面提到了其对开发者是透明的。如何获取Stub的方式值得考虑。

常见的方法是调用某个远程服务上的方法,向远程服务获取 。

JDK提供了一个RMI注册表(RMIRegistry), RMIRegistry也是一个远程对象,默认监听在传说中的1099端口上,可以使用代码启动RMIRegistry,也可以使用rmiregistry命令。

1575436071395

同时,在之前文章中遇到了rmi无法执行而ldap可以执行的情况。其原因应该就在于使用rmi的时候使用的远程对象(恶意服务器获取恶意服务器上的HTTP远程对象(也就是那个恶意的class))是没有实现三种rmi远程对象的要求的任意一种的:

1575438375514

1575438394677

1575441023028

动态类加载

  1. RMI核心特点之一就是动态类加载,如果当前JVM中没有某个类的定义,它可以从远程URL去下载这个类的class。
  2. JVM中没有某个类的定义解释:启动时将相关类的class加载入JVM中,位于JAVA文件的第三个生命周期。
  3. 动态加载的对象class文件可以使用Web服务的方式进行托管。这可以动态的扩展远程应用的功能,RMI注册表上可以动态的加载绑定多个RMI应用。
  4. 对于客户端而言,服务端返回值也可能是一些子类的对象实例,而客户端并没有这些子类的class文件,如果需要客户端正确调用这些子类中被重写的方法,则同样需要有运行时动态加载额外类的能力。客户端使用了与RMI注册表相同的机制。RMI服务端将URL传递给客户端,客户端通过HTTP请求下载这些类。
  5. JNDI注入的利用方法中也借助了动态加载类的思路

1575441696645

第一步:RMIServer注册1099的Registry服务

第二步:RMIClient想要与RMIServer通信

第三部:RMIClient通过注册表获取与Server通信的Stub连接并要求获取某个Object

第四步:RMIServer通过HTTP协议拿到WebServer的RMIClient想要的Object对象并在第五步通过Stub返回给RMIClient

JNDI注入

JNDI底层支持RMI远程对象,RMI注册的服务可以通过JNDI接口来访问和调用。

JNDI接口在初始化时,可以将RMI URL作为参数传入

在JNDI服务中,RMI服务端除了直接绑定远程对象之外,还 **可以通过References类来绑定一个外部的远程对象(当前名称目录系统之外的对象)。绑定了Reference之后,服务端会先通过Referenceable.getReference()获取绑定对象的引用,并且在目录中保存。当客户端在lookup()查找这个远程对象时,客户端会获取相应的object factory,最终通过factory类将reference转换为具体的对象实例。 **

因此在原本的rmiServer.java中多了一步

1575442572538

整个利用流程如下:

  1. 目标代码中调用了InitialContext.lookup(URI),且URI为用户可控(也就是RCE的唯一条件,后面都是攻击者设计的路);
  2. 攻击者控制URI参数为恶意的RMI服务地址,如:rmi://hacker_rmi_server//name;
  3. 攻击者RMI服务器向目标返回一个Reference对象,Reference对象中指定某个精心构造的Factory类;
  4. 目标在进行lookup()操作时,会动态加载并实例化Factory类,接着调用factory.**getObjectInstance()**获取外部远程对象实例;
  5. 攻击者可以在Factory类文件的**构造方法、静态代码块、getObjectInstance()**方法等处写入恶意代码,达到RCE的效果;

1575443059434

同时上文提供了三种可执行代码的区块分别是: 构造方法、静态代码块、getObjectInstance()

Fastjson对于JDNI的调用链:

  • -> RegistryContext.decodeObject()
    • -> NamingManager.getObjectInstance()
      • -> factory.getObjectInstance()

Spring框架的spring-tx.jar调用链

  • JtaTransactionManager.readObject()
    • InitialContext.lookup(URL)

com.sun.rowset.JdbcRowSetImpl类的execute() 调用链

  • -> JdbcRowSetImpl.execute()
    • -> JdbcRowSetImpl.prepare()
      • -> JdbcRowSetImpl.connect()
        • -> InitialContext.lookup(dataSource)

有 jdk 版本要求

需要 target 主机上面的 jdk 版本有严格的要求:

1575447960527

具体是在新版本的JDK中trustURLCodebase值为false

导致Java\jdk8u202\jre\lib\rt.jar!\com\sun\jndi\rmi\registry\RegistryContext.class#354行抛出了异常:

1575448290193

该异常调用栈为

1575448327451

因此也造成了JDK自带的修复。而LDAP修复更晚,也就是说,尽量使用ldap来使用此攻击链(能用RMI必然能用LDAP)


Java反序列化:基于CommonsCollections4的Gadget分析

在反序列化漏洞的利用过程中,攻击者会构造一系列的调用链以完成其攻击行为。如何高效的生成符合条件且可以稳定利用的攻击Payload成为了攻击链条中的重要一环,当前已经有很多现成的工具帮助我们完成Payload的生成工作如 ysoserial

基于PriorityQueue类的序列化对象的构造,另一方面是PriorityQueue对象在反序列化过程中恶意代码的触发原理

本文标题:读他人知识获得的一些知识

文章作者:

发布时间:2019年12月20日 - 19:37:52

最后更新:2019年12月31日 - 14:14:56

原始链接:http://laker.xyz/2019/12/20/%E8%AF%BB%E4%BB%96%E4%BA%BA%E7%9F%A5%E8%AF%86%E8%8E%B7%E5%BE%97%E7%9A%84%E4%B8%80%E4%BA%9B%E7%9F%A5%E8%AF%86/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。