Java反序列化中jndi注入的高版本jdk绕过
群里大佬们打哈哈的内容,菜鸡拿出来整理学习一下,炒点冷饭。
主要包含以下三个部分:
- jndi注入原理
- jndi注入与反序列化
- jndi注入与jdk版本
jndi注入原理:
JNDI(Java Name and Dictionary Interface Java名称与目录接口),一套JavaEE的标准,类似Windows注册表。
结构如下:
key:路径+名称
value:存的数据(在jndi中存的是对象Object)
jndi是java用于访问目录和命名服务的 API。使用jndi进行查询本来是一个正常的功能,但由于实现时没有考虑安全问题,如果查询了恶意对象就会导致被攻击。但是不是所有攻击都能够导致RCE(比如dnslog2333)
JNDI查询分为以下两个步骤:
1、客户端请求一个命名服务并获取对象
2、客户端解析这个对象
这两个步骤都有可能导致漏洞的发生,jndi支持LDAP、RMI、DNS、CORBA四种可能导致危险的协议,每种都对应了不同的实现方式,支持绑定的对象也包含了引用对象、反序列化对象、属性对象等等,所以攻击手段和漏洞都很多。
最常用也是最危险的攻击有jndi+rmi和jndi+ldap,corba也可以用来命令执行(但是修复得比较早,而且用corba的基本都可以用rmi)
JNDI+RMI
关键代码位于RegistryContext#lookup
public Object lookup(Name name) throws NamingException {
if (name.isEmpty()) {
return (new RegistryContext(this));
}
Remote obj;
try {
obj = registry.lookup(name.get(0));
//这里可以看到远程对象是通过rmi原生的lookup获取到的
} catch (NotBoundException e) {
throw (new NameNotFoundException(name.get(0)));
} catch (RemoteException e) {
throw (NamingException)wrapRemoteException(e).fillInStackTrace();
}
return (decodeObject(obj, name.getPrefix(1)));
可以看到远程对象是通过rmi原生的lookup获取到的,而rmi是通过反序列化获取到的远程对象,这时如果客户端系统里有gadget组件,这一步的反序列化就能导致代码执行了。
第二步,在decodeObject里面对获取到的obj进行了解析,逻辑位于RegistryContext#decodeObject
private Object decodeObject(Remote r, Name name) throws NamingException {
try {
Object obj = (r instanceof RemoteReference)
? ((RemoteReference)r).getReference(): (Object)r;
/*
* Classes may only be loaded from an arbitrary URL codebase when
* the system property com.sun.jndi.rmi.object.trustURLCodebase
* has been set to "true".
*/
//这里注释写得很清楚
// Use reference if possible
Reference ref = null;
if (obj instanceof Reference) {
ref = (Reference) obj;
} else if (obj instanceof Referenceable) {
ref = ((Referenceable)(obj)).getReference();
}
if (ref != null && ref.getFactoryClassLocation() != null &&
!trustURLCodebase) {
throw new ConfigurationException(
"The object factory is untrusted. Set the system property" +
" 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.");
}
return NamingManager.getObjectInstance(obj, name, this,
environment);
注释注明了,如果com.sun.jndi.rmi.object.trustURLCodebase为true就可以通过codebase加载
任意远程类,导致代码执行。这个校验是在jdk8u121开启的,并且是加在RegistryContext里面的,也就是只对了jndi的rmi实现作了限制,所以安全人员后续才会发掘出ldap的利用。
Object obj = (r instanceof RemoteReference) ? ((RemoteReference)r).getReference(): (Object)r;
这段代码判断传入的对象,是否满足RemoteReference接口,如果有则getReference()获取reference对象,然后进入getObjectInstance函数。
具体利用流程如下:
1、目标代码中调用了InitialContext.lookup(URI),URI为用户可控的;
2、攻击者设置uri为恶意rmi服务地址;
3、攻击者设置rmi server向目标返回一个reference引用对象,reference对象中指定了一个精心构造的Factory类;
4、目标进行lookup操作远程对象时,获取到动态加载并实例化了这个Factory类,接着调用factory.getObjectInstance()加载外部远程对象实例;
5、攻击者可以在Factory类文件的构造方法、静态代码块、getObjectInstance()方法等处写入恶意代码,达到RCE的效果;
getObjectInstance 主函数当中使用此类的getInstance()函数,即可得到系统当前已经实例化的该类对象,若当前系统还没有实例化过这个类的对象,则调用此类的构造函数。
可以引出后续的两种命令执行利用方式:
if (ref != null) {
String f = ref.getFactoryClassName();
if (f != null) {
// if reference identifies a factory, use exclusively
factory = getObjectFactoryFromReference(ref, f); //触发点1
if (factory != null) {
return factory.getObjectInstance(ref, name, nameCtx,
environment); //触发点2
}
// No factory found, so return original refInfo.
// Will reach this point if factory class is not in
// class path and reference does not contain a URL for it
return refInfo;
第一种是getObjectFactoryFromReference(),在这个函数中会通过获取到对应的恶意class对象,在开启trustURLCodebase时可以通过URLClassloader加载远程类并进行实例化,通过class.newInstance()触发恶意构造函数:
return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
第二种是通过实例化的类,调用起getObjectInstance函数,来执行恶意代码:
只要攻击者实现ObjectFactory接口,重写getObjectInstance,即可执行恶意代码。
public class Exec implements ObjectFactory {
public Exec(){}
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
System.out.println("factory.getObjectInstance hook!");
return null;
}
}
综上所述,RMI的RCE利用从某种意义上说并不是利用反序列化导致的代码执行,只是利用反序列化来加载恶意远程对象。
JNDI+LDAP
核心逻辑在ldapCtx#c_lookup
protected Object c_lookup(Name name, Continuation cont)
throws NamingException {
cont.setError(this, name);
Object obj = null;
Attributes attrs;
try {
......
if (attrs.get(Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME]) != null) {
// 序列化对象或序列化引用
obj = Obj.decodeObject(attrs);
}
if (obj == null) {
obj = new LdapCtx(this, fullyQualifiedName(name));
}
} catch (LdapReferralException e) {
......
try {
return DirectoryManager.getObjectInstance(obj, name,
this, envprops, attrs);
......
}
只看关键代码,这里主要分为两个步骤:
首先通过Obj.deocodeObject从ldap获取字符串,解码出一个ldap对象,然后通过DirectoryManager.getObjectInstance解析,这里和RMI是一样的逻辑,只是没有rmi关于trusrURLCodebase的校验。
所以jndi+ldap获取对象的方式和rmi差不多,都是通过反序列化获取的。然后解析对象调用的是DirectoryManager.getObjectInstance,其实和NamingManager.getObjectInstance基本是一样的。decodeRefernce,原生反序列化,但是如果com.sun.jndi.ldap.object.trustURLCodebase开启,会调一个重写的resolveClass进行远程类加载。
1、解析对象时调用getObjectFactoryFromReference,在开启com.sun.jndi.ldap.object.trustURLCodebase时进行远程类加载
2、和rmi一样用本地工厂类,但ldap服务端不能像rmi一样直接绑远程对象,需要绑序列化后的数据。
JNDI注入与jdk版本
jdk针对jndi注入的利用有两次修复,8u121对RMI和corba的jndi注入进行限制,com.sun.jndi.ldap.object.trustURLCodebase 限制了这两种服务加载远程工厂类。
8u191禁用了ldap的远程类加载。
至此,高版本可用的jndi注入还有:加载本地工厂类,打本地反序列化链,
前者是tomcat8/9才引入的,后者需要本地反序列化链。
总结:
1、jndi注入的原理
一般说的jndi注入原理是远程类加载。其他攻击方法还有本地工厂类代码执行、反序列化。
2、jndi注入与反序列化
jndi注入依赖反序列化来传递对象,但常说的jndi注入代码执行并不是由反序列化链导致的。同样jndi注入也可以转化成通常说的反序列化攻击。
3、jndi注入与jdk升级
jdk升级只能修复jndi远程类加载的攻击方式,高版本依然有加载本地工厂类和反序列化本地利用链的攻击方式。
Java反序列化中jndi注入的高版本jdk绕过的更多相关文章
- Java安全之JNDI注入
Java安全之JNDI注入 文章首发:Java安全之JNDI注入 0x00 前言 续上篇文内容,接着来学习JNDI注入相关知识.JNDI注入是Fastjson反序列化漏洞中的攻击手法之一. 0x01 ...
- 从高版本JDK换成低版本JDK报错Unsupported major.minor version 52.0的解决方案
从高版本JDK换成低版本JDK报错Unsupported major.minor version 52.0 java.lang.UnsupportedClassVersionError: PR/Sor ...
- 解决:高版本jdk编译低版本代码时eclipse提示Access restriction:The type 'Unsafe' is not accessible due to restriction on required library
在Eclipse中采用高版本jdk编译一些低版本的源码时,由于源码中使用了一些高版本中过时的API,可能就会报错,类似于: Access restriction:The type 'Unsafe' i ...
- 解决eclipse高版本JDK编译的项目到低版本JDK服务器上不能运行的问题
错误提示信息:Unsupported major.minor version 52.0,意思是说,当前jdk的版本不支持更高版本jdk编译出来的class文件. 我的编译环境,eclipse使用的是j ...
- 从高版本JDK换成低版本JDK报错Unsupported major.minor version 52.0
ava.lang.UnsupportedClassVersionError: PR/Sort : Unsupported major.minor version 52.0这个错误是由于高版本的java ...
- 【转】ubuntu 配置 java jdk1.8 环境,增加多版本 jdk 和切换方法
一.安装java jdk1.8 1.添加软件源 sudo add-apt-repository ppa:webupd8team/java 2.更新软件源 sudo apt-get update 3.安 ...
- 一篇博客带你轻松应对java面试中的多线程与高并发
1. Java线程的创建方式 (1)继承thread类 thread类本质是实现了runnable接口的一个实例,代表线程的一个实例.启动线程的方式start方法.start是一个本地方法,执行后,执 ...
- Java Spring 中你不知道的注入方式
前言 在Spring配置文件中使用XML文件进行配置,实际上是让Spring执行了相应的代码,例如: 使用<bean>元素,实际上是让Spring执行无参或有参构造器 使用<prop ...
- Java Spring-Bean中属性注入
2017-11-06 20:29:13 类属性的注入的三种方法 1.接口方法注入 public interface injection{ public void setName(String name ...
随机推荐
- 《吐血整理》进阶系列教程-拿捏Fiddler抓包教程(13)-Fiddler请求和响应断点调试
1.简介 Fiddler有个强大的功能,可以修改发送到服务器的数据包,但是修改前需要拦截,即设置断点.设置断点后,开始拦截接下来所有网页,直到取消断点.这个功能可以在数据包发送之前,修改请求参数:在收 ...
- 浅拷贝工具类,快速将实体类属性值复制给VO
/** * 浅拷贝的工具类 */ public class PropertiesUtil { /** * 两个类,属性名一样的元素,复制成员. */ public static void copy(O ...
- 在 C# 中使用 Span<T> 和 Memory<T> 编写高性能代码
目录 在 C# 中使用 Span 和 Memory 编写高性能代码 .NET 中支持的内存类型 .NET Core 2.1 中新增的类型 访问连续内存: Span 和 Memory Span 介绍 C ...
- ESP8266 RTOS SDK开发
ESP8266 RTOS SDK开发 目录 ESP8266 RTOS SDK开发 一.源码RTOS SDK包的下载和编译 二.固件烧录 1.管脚定义 三.程序例程 ## 1.PWM设置 连接MQTT ...
- KingbaseES V8R3 shared_buffer占用过多导致实例崩溃
背景 有这样一个案例.客户备库意外宕机,从集群日志只看出发生了主备切换,备库一直持续恢复备库没有成功,从数据库日志看到如下报错: terminating connection because of c ...
- KingbaseES sys_prewarm 扩展
Oracle 在查询数据 可以通过cache hint 所访问的数据cache 到数据库buffer,对于KingbaseES,如何将数据加载到cache 了?sys_prewarm 扩展插件可以实现 ...
- 【读书笔记】C#高级编程 第六章 数组
(一)同一类型和不同类型的多个对象 如果需要使用同一类型的多个对象,就可以使用数组或集合(后面章讲). 如果需要使用不同类型的多个对象,可以使用Tuple(元组)类型. (二)简单数组 如果需要使用同 ...
- C# 脚本与Unity Visual Scripting 交互,第一步(使用C# 脚本触发Script Graph的事件)(Custom Scripting Event)
写在前面 感谢Unity 川哥的帮助,解决了单独调用GameObject的需求 首先 需要在Unity 中创建一个自定义事件脚本(注释非常重要) using System.Collections; u ...
- PHP获取当前周一、周末时间等(持续更新)
获取周一和周日的日期 $week = date('w') == 0 ? 7 : date('w'); $Sunday = strtotime('today -' . ($week - 1) . 'da ...
- 使用kubeoperator安装的k8s 版本1.20.14 将节点上的容器运行时从 Docker Engine 改为 containerd
官方文档:https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/migrating-from-dockershim/change-runt ...