Finding gadgets like it's 2022
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.
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.
DangerousMethod
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
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)
}
}
CallsDangerousMethod
from Callable c
where c instanceof CallsDangerousMethod
select c
The VulnMethod
is a CallsDangerousMethod
callable because it uses Java reflection and calls the invoke
method which is considered dangerous.
Callable
, MethodAccess
, Method
, DangerousMethod
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
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
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
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.
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
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:
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.
@testanull on Twitter found another chain which can also be found like many others:
After the publication of our previous articleNote 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
has been done to the Ysoserial repository.The WildFlyDataSource
class is part of the org.jboss.as.connector
package and is inside the WildFly GitHub repository.
Conclusion
Leveraging the power of CodeQL can be really
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.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!