During a pentest engagement we found a Java application vulnerable to unsafe reflection [1]. This application allowed us to instantiate an arbitrary class with a controlled string passed to its constructor as argument. When we became aware of the dependencies used by the application, we posed the following question: How could we automate the process to find good classes?
For good class we mean classes that match the criteria (the constructor receives a string) and has security implications (could be used to exploit the application). This blogpost will document how we automated this process.
Motivation – CVE-2022-21724
The vulnerability we encountered during a pentest was PostgreSQL JDBC Driver Unchecked Class Instantiation (CVE-2022-21724) [9], in which an arbitrary class could be instantiated, taking a controllable argument of type string via JDBC URL. An example of payload can be seen below:
jdbc:postgresql://127.0.0.1:5432/testdb?&socketFactory=CLASS&socketFactoryArg=ARGUMENT
When looking for writeups we found some interesting attacks [10] [11] using classes from Spring and other libraries, such as:
- org.springframework.context.support.ClassPathXmlApplicationContext
- org.springframework.context.support.FileSystemXmlApplicationContext
- com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext
- com.bea.core.repackaged.springframework.context.support.ClassPathXmlApplicationContext
- com.tangosol.coherence.mvel2.sh.ShellSession
However none of these classes were available in our target. Fortunately we managed to get access to the list of libraries used by the application with another vulnerability and with that we decided to automate the process of finding good classes for our attack.
Static Analysis
To address our problem we can employ static analysis with data flow, which aims to identify paths from data sources to data sinks where processing occurs. In this particular scenario, we define our sources and sinks as follows:
source – classes with constructors that accept string as arguments
sink – any method that could be used to exploit the application
After understanding the problem and defining our sources and sinks, we realized the need for a tool to aid in this analysis. Since we anticipated the need to analyze dependencies in cases where only the compiled Java code would be available, we sought a tool capable of running analysis directly on JAR files. Consequently, we opted to use Joern [2], which offers support for JAR files, as seen in [3].
Joern
For those who are unfamiliar with Joern, it is a code analysis platform that utilizes a structure known as Code Property Graph (CPG) [4]. This platform enables us to execute queries on code using a Scala-based language, facilitating comprehensive code analysis. We can also use Joern to perform dataflow analysis, for instance, take a look at this code snippet:
import java.io.IOException;
public class Example1 {
public Example1(String arg) throws IOException {
foo(arg);
}
public void foo(String arg) throws IOException {
Runtime.getRuntime().exec(arg);
}
}
We can use the following Joern script to find the path from source to sink:
@main def main(code: String) = {
importCode(code)
// Get constructors that accepts string as arg
val cons = cpg.method.isConstructor.signatureExact("void(java.lang.String)").l
// Get all calls to Runtime.exec
val name = "java.lang.Runtime.exec:java.lang.Process(java.lang.String)"
val calls = cpg.method.fullNameExact(name).callIn.l
// Source is the string passed to constructor
val source = cons.parameter.order(1).l
// Sink is the argument passed to Runtime.exec
val sink = calls.argument(1).l
// Print the path from source to sink
sink.reachableByFlows(source).p
}
When running the script, we should obtain a result similar to the table below:
joern --script example1.sc --params code=Example1.java
nodeType | tracked | lineNumber| method | file
===================================================================================
MethodParameterIn | <init>(this, String arg) | 4 | <init> | <unknown>
Identifier | this.foo(arg) | 5 | <init> | <unknown>
MethodParameterIn | foo(this, String arg) | 9 | foo | <unknown>
Identifier | Runtime.getRuntime().exec(arg) | 10 | foo | <unknown>
This example illustrates the type of analysis we will be conducting, with the distinction that instead of running it against the source code, we will execute it against the compiled code. It is worth mentioning that during our tests, we encountered an issue with Joern where it failed to capture paths when the source data was assigned to a class’s field member in compiled code. We have reported this issue [5] [6].
Experiments
To validate the process of identifying suitable classes, we conducted experiments on several well-known Java libraries. To accomplish this, we used the following methodology:
- Enumerate interesting sinks
- Download the libraries
- Static analysis approach
- Dynamic analysis approach
Enumerate interesting sinks
To choose the sinks to be used in the dataflow analysis, we enumerated some of the most common types of vulnerabilities and attacks in Java applications with direct server-side impact. The list can be seen below:
- Command Injection
- Expresion Language Injection (EL, OGNL, MVEL, SpEL, JEXL)
- Deserialization
- File read/write
- JDBC Driver Connect
- SSRF
- JNDI
- XML Beans
- XXE
- SQL Injection
- Code Loading (System.loadLibrary, java.net.URLClassLoader etc)
- Code Eval
Then, for each one, we select a set of methods that can lead to such vulnerabilities when called with user-controlled data.
Download the libraries
We created a script (https://github.com/convisolabs/java_unsafe_reflection/blob/main/downloader/download.py) to download the most relevant Java libraries in order to run our experiments. In short, it goes over an ordered list [8] of popular/relevant libraries and downloads each JAR file to a local directory.
Static analysis approach
After enumerating the sinks and downloading the JARs, we have developed a Joern script (https://github.com/convisolabs/java_unsafe_reflection/blob/main/joern_scripts/all_sinks_each_file.sc) that accepts two folders as arguments. One folder represents the location of the JAR files, while the other folder is designated for storing the results.
To execute the script, use the following command:
joern --script all_sinks_each_file.sc --params inDir=jars,outDir=result
The script will generate the results in JSON format. The output files will contain paths that link sources to sinks. To enhance readability, we have also created a separate script (https://github.com/convisolabs/java_unsafe_reflection/blob/main/utils/pretty_print_result.py) that facilitates the interpretation of the output.

Some of the problems we had when developing and running this analysis were:
- We encountered an issue with Joern where it failed to capture paths when the source data was assigned to a class’s field member in compiled code [5] [6];
- It was slow if you did not have a good amount of RAM memory available. Also, bigger files would take more processing time. We skipped some of the larger libraries we downloaded;
- We encountered some Java constructs that were not handled well by Joern and would make it unable to correctly track data flow, leading to miss good constructors (false negatives).
Despite these problems, the analysis was still able to find good results.
Dynamic analysis approach
One way to validate the results would be to directly access the source code (if available) or decompile the JAR file and attempt to follow the identified paths. However, this approach would only be feasible for a limited number of results or when the paths are relatively small, which is not the case in our scenario. Instead, we opted for dynamic validation. We developed a code that accepts a class and its corresponding argument, and attempts to instantiate it.
We then automated the process of retrieving the results, extracting the constructors, and instantiating them using a pre-established list of arguments. This list consisted of values carefully selected to validate various scenarios, including accessing servers, creating files, executing system commands, and more. We also logged all execution to be able to manually inspect it. The script can be seen at https://github.com/convisolabs/java_unsafe_reflection/blob/main/utils/tester.py.

In the GIF, the upper part of screen shows the tester.py script running and the lower part shows a live inspection of the generated log (tail -f /tmp/tester.dd-mm-yyyy_HHhMMmSSs.log).
This dynamic validation approach allowed us to verify the findings more efficiently and effectively.
Findings
Due to the amount of results obtained, we reviewed only a small part of them and below are just a few of the interesting constructors we found:
MySQL Client Arbitrary File Reading
Package: mysql-connector-java-8.0.11.jar
Class: com.mysql.cj.jdbc.admin.MiniAdmin
Arg: jdbc:mysql://rogue_server
XXE, SSRF and Arbitrary File Reading
Package: mybatis-3.5.13.jar
Class: org.apache.ibatis.parsing.XPathParser
Arg: XXE exfiltrate payload
Insecure Deserialization
Package: jasperreports-6.20.5.jar
Class: net.sf.jasperreports.export.SimpleExporterInput
Arg: file path to serialized exploit
Arbitrary File Content Leak Through Error
Package: dnsjava-3.5.2.jar
Class: org.xbill.DNS.tools.jnamed
Arg: /etc/passwd
SSRF
Package: itextpdf-5.5.13.3.jar
Classes:
- com.itextpdf.text.pdf.codec.GifImage
- com.itextpdf.text.pdf.RandomAccessFileOrArray
Arg: URL
SSRF
Package: itext-4.2.1.jar
Classes:
- com.itextpdf.text.ImgWMF
- com.lowagie.text.pdf.codec.GifImage
Arg: URL
Possible Webroot Path Leak Through Error
Package: hutool-core-5.8.19.jar
Class: cn.hutool.core.io.file.FileReader
Arg: some non existing file name (e.g. xxx)
Command Injection
Package: jline-2.14.6.jar
Classes:
- jline.internal.TerminalLineSettings
- jline.UnixTerminal
Arg: $(command)
Conclusion
In this blog we presented a simple approach to automate the discovery of interesting classes to be used when attacking unsafe reflection in Java applications. We showed how to use Joern to execute a customized dataflow analysis suited for our needs.
An interesting research titled “A New Attack Interface In Java Applications” presented by Xu Yuanzhen and Peter Mularien at Blackhat Asia 2023 [7] shows other examples of the process of finding good classes to exploit similar vulnerabilities via JDBC URLs.
Follow-up work could be to run our analysis in the Java code itself, to discover interesting constructors that do not depend on an external library. Another one could be to take advantage of the script to find security vulnerabilities in Java applications by just redefining the source to represent user-controlled data such as HTTP request parameters, for example.
Authors:
Gabriel Quadros – Security Analyst
Ricardo Silva – Security Analyst

References
- https://owasp.org/www-community/vulnerabilities/Unsafe_use_of_Reflection
- https://github.com/joernio/joern
- https://joern.io/blog/plume-brings-jvm-bytecode-support-to-joern/
- https://ieeexplore.ieee.org/document/6956589
- https://github.com/joernio/joern/issues/2789
- https://github.com/joernio/joern/issues/2791
- https://i.blackhat.com/Asia-23/AS-23-Yuanzhen-A-new-attack-interface-in-Java.pdf
- https://libraries.io/search?languages=Java&order=desc&platforms=Maven&sort=rank
- https://nvd.nist.gov/vuln/detail/CVE-2022-21724
- https://pyn3rd.github.io/2022/06/02/Make-JDBC-Attacks-Brilliant-Again/
- https://github.com/vulhub/vulhub/blob/master/weblogic/CVE-2020-14882/README.md