Finding gadgets like it's 2022

Written by Hugo Vincent - 14/03/2022 - in Pentest - Download

So you have found an application vulnerable to Log4Shell, but the bypass gadgets are not working, and you did not manage to use a gadget from Ysoserial? If you read our last articles on finding Java gadgets you might have found a new one with gadget inspector. But what if gadget inspector did not find a valid chain? You might stop and be desperate because, as we saw, manual gadget research is not an easy task!

In this article we will present a new methodology and multiple CodeQL queries to find gadget chains in Java applications. We'll explain how to leverage the power of CodeQL to provide an alternative to gadget inspector and to illustrate this we will present a new Java gadget that we found with our tool QLinspector.

CodeQL

What is CodeQL?  From their documentation:

Discover vulnerabilities across a codebase with CodeQL, our industry-leading semantic code analysis engine. CodeQL lets you query code as though it were data. Write a query to find all variants of a vulnerability, eradicating it forever.

In other terms CodeQL is a very powerful static code analyzer that provides a way to analyze code by making queries. It's very useful when you're searching for vulnerabilities in white box. You can search for vulnerable patterns in your code by creating new queries or by using existing ones. It also provides data flow functionality to follow data across function calls, this can be useful to find injection vulnerabilities for example.

 

Translating the problem to CodeQL

First we need to translate the problem of finding Java gadgets to CodeQL. A gadget chain is a chain of function calls from a source method, generally readObject, to a sink method which will perform dangerous actions like calling the exec method of the Java runtime.

Here, we defined the three main components:

  • The source
  • The sink
  • The chain

Finding new sinks

Sink methods are the dangerous methods that we want to reach. We can define them in CodeQL like this:


private class RuntimeExec extends Method {
  RuntimeExec(){
    hasQualifiedName("java.lang", "Runtime", "exec")
  }
}

 

Here we define a new CodeQL class called RuntimeExec. Since this class extends the Method class this will match all the method which are called exec, defined in the Runtime class of the java.lang package.

Defining new sinks is quite easy. We define a DangerousMethod class containing each dangerous type of sink. New ones can then be added with a new check to that class:


class DangerousMethod extends Callable {
  DangerousMethod(){
    this instanceof ExpressionEvaluationMethod or
    this instanceof ReflectionInvocationMethod or
    this instanceof RuntimeExec or
    this instanceof URL or
    this instanceof ProcessBuilder or 
    this instanceof Files or
    this instanceof FileInputStream or 
    this instanceof FileOutputStream or
    this instanceof EvalScriptEngine or
    this instanceof ClassLoader or
    this instanceof ContextLookup

    }
}

 

We can search for calls to those methods with the MethodAccess class which represent a call to a method. For example the following query will return all the calls to the exec method:


from MethodAccess ma
where ma.getMethod() instanceof RuntimeExec
select ma
exec
Example of potentially interesting method call.

Note that the code used in our examples can be found on our GitHub.

We want all the methods that calls a DangerousMethod, so we look for MethodAccess of dangerous methods, and we use the enclosing callable (the method which makes the call to the dangerous method) as a result:


private class CallsDangerousMethod extends Callable {

  CallsDangerousMethod(){

    exists(MethodAccess ma | ma.getMethod() instanceof DangerousMethod and ma.getEnclosingCallable() = this)

  }

}

Having precisely described the object we are looking for, the CallsDangerousMethod, it is straightforward to request them in our code base:


from Callable c
where c instanceof CallsDangerousMethod
select c
callsdangerousmethod
Example of potential sink.

The VulnMethod is a CallsDangerousMethod callable because it uses Java reflection and calls the invoke method which is considered dangerous.

Callable, MethodAccess, MethodDangerousMethod all of this can be quite confusing if it's your first time using CodeQL. Playing a bit with this tool can help to understand those queries.

Finding new sources

A source is a method that we can call to start the gadget chain, the first obvious one is readObject but there are other methods like:

  • readObjectNoData
  • readResolve
  • readExternal
  • ...

Theses are methods that can be called when an object is deserialized so any Serializable class with one of these method is a valid starting point.

However, there are other known methods that we can call. From our previous article we know that we can call the get method of an arbitrary map. We can also think to hashCode, equals, compare...

All theses methods can be added to the source starting point:


class Source extends Callable{
    Source(){
        getDeclaringType().getASupertype*() instanceof TypeSerializable and (
            this instanceof MapSource or 
            this instanceof SerializableMethods or
            this instanceof Equals or
            this instanceof HashCode or
            this instanceof Compare or
            this instanceof ExternalizableMethod or 
            this instanceof ObjectInputValidationMethod or
            this instanceof InvocationHandlerMethod or
            this instanceof MethodHandlerMethod or
            this instanceof GroovyMethod
        )
    }
}

Note that the declaring type of the source method must be serializable otherwise it can't be deserialized.

We can search for all instances of Source:


from Callable c
where c instanceof Source
select c
source
Example of potential starting point for a chain.

As you can see this hashCode method is a source but is also a sink which makes the gadget easy to spot.

Linking the sources to the sinks

We have the Source and the Sink now we need to link and find a path between them.

Most of the examples queries of the Security directory of the Java CodeQL repository are using data flow or tainted analysis. This is not relevant in our case because we are not interested in following a specific data since we control all the fields of the deserialized objects.

Indeed, a valid chain would be a method which is an instance of Source which calls one or several methods and the last one is an instance of CallsDangerousMethod. In CodeQL this can be done with the polyCalls method:


from Callable source, Callable sink 
where source instanceof Source and
sink instanceof CallsDangerousMethod and 
source.polyCalls(sink)
select  source, sink
chain1
Call chain of multiple methods leading to a sink.

But this need to be recursive because a gadget chain is not just the source calling the sink:


private class RecursiveCallToDangerousMethod extends Callable {
  RecursiveCallToDangerousMethod(){

    (
      getDeclaringType().getASupertype*() instanceof TypeSerializable or 
      this.isStatic() 
    ) 
    
    and
    
    (
     this instanceof CallsDangerousMethod or
    exists(RecursiveCallToDangerousMethod unsafe | this.polyCalls(unsafe))
    ) 
  }
}

We are only looking for methods of classes which are serializable or static since we can call static methods from our deserialization flow like in the Click1 gadget chain. Note that this statement is partially true, we can also call methods of classes which are not serializable. Here is a little example:


private void readObject(java.io.ObjectInputStream in) {
    in.defaultReadObject();
    dangerousObject = new DangerousObject();
    dangerousObject.execDangerousMethod()
}

Removing the serializable check is possible, it  will increase the number of false positive but can yield new results depending on what you want.

Finally, a Callable is considered as a RecursiveCallToDangerousMethod if it's a CallsDangerousMethod expression or if it calls another Callable which is a RecursiveCallToDangerousMethod.

Now we have everything to find new chains.

Re-discovering old chains

We downloaded Java libraries which are known to contain gadget chains like the ones from Ysoserial and run our queries to validate that our technique is indeed working on real projects.

Click1

There is a chain in the Click library in version 2.3.0 the full chain is:


java.util.PriorityQueue.readObject()
      java.util.PriorityQueue.heapify()
        java.util.PriorityQueue.siftDown()
          java.util.PriorityQueue.siftDownUsingComparator()
            org.apache.click.control.Column$ColumnComparator.compare()
              org.apache.click.control.Column.getProperty()
                org.apache.click.control.Column.getProperty()
                  org.apache.click.util.PropertyUtils.getValue()
                    org.apache.click.util.PropertyUtils.getObjectPropertyValue()
                      java.lang.reflect.Method.invoke()
                        com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties()

First we need to find sinks because, if there are no sinks we can stop here we won't be able to find a gadget chain and can stop here:


from Callable c0,  MethodAccess ma
where c0 instanceof RecursiveCallToDangerousMethod and
ma.getMethod() instanceof DangerousMethod and 
ma.getEnclosingCallable() = c0
select c0, ma
click2

Then we can start looking for sources:


from Callable c0
where c0 instanceof RecursiveCallToDangerousMethod and
c0 instanceof Source
select c0

Note that the source must also be a RecursiveCallToDangerousMethod because we are not interested in sources which don't lead to a dangerous method.

click3

We have one result, but what does it mean? It simply means that there is a chain between the call to the compare method and at least one of the sinks we previously found. Now we need to fill the chain with the intermediate calls.

Unfortunately this is manual and can't be automated because we don't know in advance how many intermediate calls will be present, so we start with 0 intermediate call:


from Callable c0, MethodAccess ma
where c0 instanceof RecursiveCallToDangerousMethod and

ma.getMethod() instanceof DangerousMethod and 
ma.getEnclosingCallable() = c0 and

c0 instanceof Source 

select c0, ma

There won't be any result for this query because the source and the sink are not the same. Thus, we add intermediate calls until we find a chain:


from Callable c0, Callable c1, Callable c2, Callable c3, Callable c4, 
MethodAccess ma

where c0 instanceof RecursiveCallToDangerousMethod and
ma.getMethod() instanceof DangerousMethod and 
ma.getEnclosingCallable() = c0 and

c1.polyCalls(c0) and
c1 instanceof RecursiveCallToDangerousMethod and

c2.polyCalls(c1) and
c2 instanceof RecursiveCallToDangerousMethod and

c3.polyCalls(c2) and
c3 instanceof RecursiveCallToDangerousMethod and

c4.polyCalls(c3) and
c4 instanceof RecursiveCallToDangerousMethod and

c4 instanceof Source 

select  c4, c3, c2, c1, c0, ma
click1

This chain is the same as the one from Ysoserial. The last method is getObjectPropertyValue which calls any getter method via reflection. This can be used in combination with the TemplatesImpl to execute arbitrary command.


File: PropertyUtils.java
187:     private static Object getObjectPropertyValue(Object source, String name, Map cache) {
188:         PropertyUtils.CacheKey methodNameKey = new PropertyUtils.CacheKey(source, name);
189: 
190:         Method method = null;
191:         try {
192:             method = (Method) cache.get(methodNameKey);
193: 
194:             if (method == null) {
195: 
196:                 method = source.getClass().getMethod(ClickUtils.toGetterName(name));
197:                 cache.put(methodNameKey, method);
198:             }
199: 
200:             return method.invoke(source);

Indeed, this is a known trick. The TemplatesImpl class is part of the JDK and if we manage to call the getOutputProperties method, we can load an arbitrary class and execute arbitrary commands. More details about this trick can be found here.

Note that when you find a valid chain leading to one sink you can keep adding intermediate calls to find all the possible chains leading to all the sinks.

ROME / Hibernate / Mojarra

As we saw in the Click1 example, the process is a bit manual, we need to go through each of these steps:

  • check the presence of a sink
  • check the presence of a source leading to a sink
  • find the full chain

We repeated this process on other known vulnerable Java library to re-discover known chains like the ROME, Hibernate1 and the Mojarra chain from our previous article.

For the Hibernate1 chain we also found multiples variants of the original gadget like the one in red:

hibernate2

 


org.hibernate.cache.spi.QueryKey.readObject
        org.hibernate.cache.spi.QueryKey.generateHashCode
              org.hibernate.type.ComponentType.getHashCode
                    org.hibernate.type.ComponentType.getPropertyValue
                          org.hibernate.type.ComponentType.getPropertyValue
                                org.hibernate.property.access.spi.GetterMethodImpl.get
                                      java.lang.reflect.Method.invoke

This chain is valid for the version 5.6.5 of hibernate like the original one.

The same goes for Mojarra. After the publication of our previous article @testanull on Twitter found another chain which can also be found like many others:

mojarra2

Note that gadget inspector didn't manage to find the ROME and Hibernate1 chain on their respective compiled jar.

New chain

Finding old gadgets is nice, but it's time to find a new one.

We run our queries on the Wildfly project. Wildfly is a Java application server, with more than 10000 Java classes.

By running the source query we can find the following readObject method:


File: WildFlyDataSource.java
113:     private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
114:         in.defaultReadObject();
115:         jndiName = (String) in.readObject();
116: 
117: 
118:         try {
119:             InitialContext context = new InitialContext();
120: 
121:             DataSource originalDs = (DataSource) context.lookup(jndiName);
[...]

This is obviously vulnerable to a JNDI injection. The exploitation code can be found on our GitHub and a pull request has been done to the Ysoserial repository.

The WildFlyDataSource class is part of the org.jboss.as.connector package and is bundled inside the WildFly GitHub repository.

Conclusion

Leveraging the power of CodeQL can be really helpful to find Java gadget chains. However, there are some limitations. First you need to have the source code of the project you want to analyze, and you need to be able to compile it otherwise it won't work with CodeQL. Another limitation is that CodeQL can only analyze one project at a time. You can't mix multiple libraries to find chains using both unlike gadget inspector that is able to do it, provided that you feed it with a single fat jar.

Nevertheless, finding source code of Java libraries is often possible and compiling it is quite easy with maven, Gradle or ant which make this approach still powerful.

We hope we brought as many details as possible on the process of finding new java gadgets with the help of CodeQL. Now it's time to find new ones, and please, don't forget to share what you find!

All the queries are available on our GitHub project QLinspector1.