java反序列化JNDI篇

概念:

简答来说可以类比为map中的键值对,就是会给每一个对象对应一个名称,可以通过名称去找到指定对象
漏洞名称叫做jndi注入,但其实和注入并没有太大关系,大体来说应该是可控参数存在,导致远程类加载执行恶意代码
官方文档(JNDI):
https://docs.oracle.com/javase/tutorial/jndi/overview/index.html

结构包装:

JNDI 包含在 Java SE 平台中。要使用 JNDI,您必须具有 JNDI 类和一个或多个服务提供者。JDK 包括以下命名/目录服务的服务提供商:
● 轻量级目录访问协议 (LDAP)
● 通用对象请求代理架构 (CORBA) 通用对象服务 (COS) 名称服务
● Java 远程方法调用 (RMI) 注册表
● 域名服务 (DNS)

JNDI+RMI

1.调用方法
首先开启RMIServer服务

然后开启JNDIRMIServer

最后开启客户端JNDIRMIClient

最后JNDIRMIServer出现回显

其实仔细看一下代码,发现答题逻辑上和rmi调用其实是一样的,唯一不同的就是多了一个initialContext函数的调用
那为什么要调用这个函数呢,又为什么说这样调用就会存在jndi注入呢?
我们可以对比一下RMIClient和JNDIRMIClient的代码
RMIClient

JNDIRMIClient

我们可以发现两个代码中lookup中的内容并不相同,而在rmi之前的学习中我们可以知道lookup这里是存在可以序列化和反序列化的地方的,也就是说存在可能出现恶意代码加载的地方,但是由于RMIClient当中lookup只能查询远程对象名称,是无法控制远程地址的,所以在rmi当中是无法实现的,而在JNDIRMIClient当中我们可以发现lookup是可以控制远程地址以及远程对象,也就存在利用的地方
然后这里就存在另一个问题就是,上面所说的一切的前提是lookup的加载方法是rmi的原生方法才能实现,所以我们动态调试更进去看一下,发现果真如此

看到这里我们发现就是调用的ResgistryImpl_Stub中的lookup方法,所以说存在查询恶意对象导致的远程代码执行
但其实这并不是真正意义上的jndi注入,只是这里存在一个恶意代码的利用点,真正的jndi注入是发生在引用类型

JNDIRMIServer

TestRef是恶意引用类,后面的是远程对象地址
之所以用这个是因为当我们的恶意类不满足条件时,我们可以用引用做一个转换,将我们要执行的恶意代码包含在引用当中
跟到Referce中看一看参数

变量字面意思是工厂和工厂位置,其实就可以用他包裹住我们的恶意代码进而调用它的时候去执行我们的恶意代码
这时候我们写一个弹计算机的恶意代码,编译好之后,开启三个服务,就会在客户端上弹出计算器

python在恶意类目录下开启http服务就能弹计算器了

在这个过程中,JNDIRMIServer和RMIServer相当于是恶意机器,而JNDIRMIClient相当于是存在漏洞的机器,而这个存在漏洞的机器事实上是需要一个lookup方法就行了
整个跟代码的流程:(简述)
前面我们已经分析过的实际上调用的还是rmi原生lookup方法
然后lookup方法得到一个对象,是ReferenceWrapper_Stub对象,但是实际上绑定的是Reference对象,所以说问题就出在了JNDIRMIServer中rebind方法
跟进去看之后发现在调用rebind方法的时候,我们传入的方法会被encodeObject方法处理一下,说白就是封装了一下
然后encodeObject方法也很简单就是当对象是Reference时,就会包装成一个ReferenceWrapper
然后接着上面客户端的分析,我们发现lookup方法得到一个ReferenceWrapper_Stub对象,然后经过处理之后就会被decodeObject转回成Reference
但是在这个过程中(RegistryContext类中)我们是没有发现其对恶意类进行初始化的操作的,所以只能接着往下分析
继续跟进去decodeObject中,然后里面return的时候会走入NamingManager中的getObjectInstance方法中
跟进去getObjectInstance方法中发现了一个关键方法getObjectFactoryFromReference方法,字面意思就是从引用中获取工厂方法
然后跟到这个方法里面就会找到工厂类加载的地方helper.loadClass(factoryName)
然后loadClass我们也知道先会使用AppClassLoader,在本地寻找,找不到的话看代码就会进一步中从codebase中去获取,找到之后就会类加载helper.loadClass(factoryName,codebase)
然后往下面看发现新建了一个URLClassLoader,会将codebase传入,同时进行了newInstance实例化(因为恶意代码是在构造函数当中,所以必须要实例化)
又因为实例化在getObjectFactoryFromReference方法中,所以最后实例化就执行了代码

JNDI+LDAP

前情概要:

ldap并不是固定在java中的东西,而是一个用于访问和管理目录服务的协议
而在最之前我们是发现jdk是包含此服务的,所以产生了该漏洞
在jdk8u121之后修复了rmi和COBAR的攻击点,但是漏了一个ldap(jdk1.8_191)

复现

JNDILDAPServer:

首先使用apache Directory Studio创建一个LDAP服务
然后创建一个客户端JNDILDAPClient

然后JNDILDAPServer和JNDILDAPClient先后运行起来,就弹计算器了

原理分析:

照常从客户端的lookup开始断点调试吗,一路跟下去,最终会走到LdapCtx下的c_lookup方法中去,然后在这个方法中会调用DirectoryManager下的getObjectInstance方法
然后进来之后就和JNDI+RMI后半段是一样的,会进入getObjectFactoryFromReference中去,最后恶意类初始执行代码

JNDI+JDK高版本绕过

在jdk8u191之后,ldap也被修复了,修复的地方在于当进行codebase远程类加载的时候会有一个限制条件
就是只有当trustURLCodebase的值为true的时候,才能进行远程类加载,但是默认是false
这时候就会像如果不能加载远程对象,那我们想是否会有本地工厂可以供我们来利用呢?事实证明,的确有
在tomcat的核心包当中,有一个BeanFactory,他的getObjectInstance方法存在一个invoke,也就是存在反射调用,当参数可控的时候,我们也可以rce
首先运行RMIServer

然后再写一个JNDIRMIServerBypass

import javax.naming.InitialContext;
import javax.naming.StringRefAddr;
import java.security.spec.ECField;

public class JNDIRMIServerBypass {
    public static void main(String[]args)throws Exception{
        InitialContext initialContext=new InitialContext();
        //ResourceRef ref=new ResourceRef("java.el.ELProcessor",null,"","",true,"org.apache.naming.factory.BeanFactory",null);
        ref.add(new StringRefAddr("forceString","x=eval"));
        ref.add(new StringRefAddr("x","Runtime.getRuntime().exec('calc')"));
        initialContext.rebind("rmi://127.0.0.1:1099/remoteObj",ref);
    }
}

我们在分析JNDI+RMI的时候可以记的在初始化类加载之前loadclass会先使用Apploadclass在本地查找,之前是找不到所以远程加载,但是现在是可以在本地知道的BeanFactory,最后实例化的时候就可以得到一个BeanFactory类
然后在执行factory.getObjectInstance的是或就会执行BeanFactory的getObjectInstance方法,而他里面会有反射调用
然后里面大致过程就是有个ra会先从引用中获取forceString,我们传入的值是eval,然后当反射调用ra的时候,会执行里面的方法,我们传入的值是Runtime.getRuntime().exec('calc'),最后就会导致代码执行
图解示例

posted @ 2025-02-28 16:50  Zephyr07  阅读(53)  评论(0)    收藏  举报