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 ...
随机推荐
- java-多态、内部类
1.多态: 1)意义: 1.1)同一类型的引用,指向不同的对象时,有不同的实现-------行为的多态: cut(),run(),teach()... 1.2)同一个对象,被造型为不同的类型时,有不同 ...
- ENSP NE40E 报错(NE1启动操作超时,请检查与服务器链接后重试!)
前言:某网友淘宝找我咨询NE40E启动失败的问题,事后我整理的处理过程,供各位同行参考. 系统版本:windows 10 软件版本: ENSP:V100R003C00 virtual BOX:5.2. ...
- 第九十八篇:Web的储存机制LocalStorage
好家伙 1.什么是LocalStorage? LocalStorage 是一种 web 端的存储机制, 它使得由 JavaScript 编写的网站或者应用可以无限期的在浏览器中存储并访问数据. Loc ...
- 第七十五篇:Vue兄弟组件传值
好家伙, 兄弟组件的传值用到Eventbus组件, 1.EventBus的使用步骤 ① 创建 eventBus.js 模块,并向外共享一个Vue的实例对象 ②在数据发送方, 调用bus.$emit(' ...
- 手撸Router,还要啥Router框架?react-router/vue-router躺一边去
有没有发现,在大家使用React/Vue的时候,总离不开一个小尾巴,到哪都得带着他,那就是react-router/vue-router,而基于它们的第三方框架又出现很多个性化约定和扩展,比如nuxt ...
- 第七章 完成kubernetes集群部署并验证
在其中一运算个节点上操作就行 创建nginx资源配置清单的yaml文件 [root@hdss7-21 ~]# vim /opt/kubernetes/conf/nginx-ds.yaml apiVer ...
- PostgreSQL 与 Oracle 访问分区表执行计划差异
熟悉Oracle 的DBA都知道,Oracle 访问分区表时,对于没有提供分区条件的,也就是在无法使用分区剪枝情况下,优化器会根据全局的统计信息制定执行计划,该执行计划针对所有分区适用.在分析利弊之前 ...
- [Python]-torchvision.transforms模块-图像预处理
PyTorch框架中常用torchvision模块来辅助计算机视觉算法的搭建,transforms用于图像的预处理. from torchvision import transforms 预处理操作集 ...
- 在Windows 2012 R2上安装vcenter 5.5
在Windows 2012 R2上安装vCenter 5.5做个实验,发现安装的时候卡在Install Directory service了. 重启后,再装也一样. 上网查了一下,说是要装好ADLDS ...
- 《Markdown常用语法及快捷键》
Markdown常用语法及快捷键 [```]+空格--代码格式 [---]+空格--水平分割线 [&emsp]+[;]--空格 [shift]+[tab]--清除当前行的格式