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 ...
随机推荐
- Ceph 块存储 创建的image 映射成块设备
将创建的volume1映射成块设备 [root@mysql-server ceph]# rbd map rbd_pool/volume1 rbd: sysfs write failed RBD ima ...
- 面试常问:HTTP 1.0 和 HTTP 1.1 有什么区别?
这篇文章会从下面几个维度来对比 HTTP 1.0 和 HTTP 1.1: 响应状态码 缓存处理 连接方式 Host头处理 带宽优化 响应状态码 HTTP/1.0仅定义了16种状态码.HTTP/1.1中 ...
- 关于virtio_net网卡命名的小问题
最近看了一个小问题,涉及到一致性网络设备命名(Consistent Network Device Naming),在此记录一下. 系统是 4.18.0-240.el8.x86_64,centos 8. ...
- 若依代码生成的一个大坑 You have an error in your SQL syntax; check the manual that corresponds to your MySQL s
报错如下所示:显示我的xml文件的SQL语句有错 ### Error querying database. Cause: java.sql.SQLSyntaxErrorException: You h ...
- Git Rebase-提交整洁之道
git rebase git rebase是一个非常有用的命令,但知道和用的人非常少,今天介绍一下其作用 git rebase -i 作用:常用来合并多个相同目的的提交. 交互式有下面几个命令,常用命 ...
- Python数据科学手册-Pandas:数值运算方法
Numpy 的基本能力之一是快速对每个元素进行运算 Pandas 继承了Numpy的功能,也实现了一些高效技巧. 对于1元运算,(函数,三角函数)保留索引和列标签 对于2元运算,(加法,乘法),Pan ...
- 第一个Django应用 - 第四部分:表单和类视图
一.表单form 为了接收用户的投票选择,我们需要在前端页面显示一个投票界面.让我们重写先前的polls/detail.html文件,代码如下: <h1>{{ question.quest ...
- C#-13 泛型
一 泛型 泛型提供了一种更优雅的方式,可以让多个类型共享一组代码.泛型允许我们声明类型参数化的代码,可以用不同的类型进行实例化. 也就是说,我们可以用"类型占位符"来写代码,然后在 ...
- P3919 【模板】可持久化线段树 1(可持久化数组)
还是用主席树来做(因为提到不同的版本),这时候的主席树不是以权值为下标的,就是普通的线段树,维护范围1~n,i存的是a[ ]中的数. 1 #include <bits/stdc++.h> ...
- 通过linux-PAM实现禁止root用户登陆的方法
前言 在linux系统中,root账户是有全部管理权限的,一旦root账户密码外泄,对于服务器而言将是致命的威胁:出于安全考虑,通常会限制root账户的登陆,改为配置普通用户登陆服务器后su切换到ro ...