When researching for another project this week, I came across a couple of CVEs, with no exploits, for Apache’s Any23 service. As I couldn’t find any exploit code online, I decided to try and write my own.
One CVE, CVE-2021-40146 is a RCE vulnerability, with no exploit code online. Follow the link and you’ll see that the vuln is apparently in the
I fired up Eclipse and loaded the project straight from Github. That vuln is present before version 2.5, so I then reverted the codebase to 2.4 and after building the project, I tested the CLI tool on some example files.
This is in fact an Insecure Deserialization vulnerability as YAML is a file format for serializing data. The line of code which caused the vulnerability calls a YAML parser through
loadAll() to ingest the document and create any Java object it finds, with no restrictions.
YAMLExtractor.java and found the offending line:
And here is the commit which patched the behaviour of the YAML parser to mitigate this vulnerability:
I checked out what the
SafeConstructor is all about on the SnakeYAML github. The safety it provides is a form of whitelist approach, where only the Java objects of basic types like strings, booleans and floats etc are allowed to be constructed, meaning the parser won’t handle a custom Java class.
In general and specifically for SnakeYAML, this is how you secure against Insecure Deserialization: you tightly control what can be serialized from incoming YAML files. An alternative to the
SafeConstructor is any other specific Class constructor which doesn’t expect arbitrary Java objects.
The way to exploit Insecure Deserialization vulnerabilities is to send serialised code which then gets executed when it is deserialized. I found an example on this excellent writeup:
There is a syntax in YAML where you use !! to specify datatypes and also for Java classes.
The above YAML, when deserialized without protection or without using the
SafeConstructor, will create the
In Java deserialization vulnerabilities, only certain classes are available for us to reconstruct without simply throwing errors, and these vary across YAML libraries. They are called “Gadgets” – the ScriptEngineManager is our gadget for SnakeYAML, as detailed (and much more besides) at the Marshalsec Github.
I tested it with a Burp Collaborator token and sure enough, I get some hits!
I’m not quite sure why the class executes immediately, but I think it’s to do with Java’s Service Provider mechanism. Anyway, currently this is technically RCE but really it’s just CSRF – we can get the server to fetch a resource. Let’s upgrade to full RCE.
The idea behind the above class is to fetch required Java classes. This functionality has some arbitrary expectations as detailed in the write-up above and here, but essentially it will open the resource pointed to by the URL and expect a .jar file. If it finds a .jar, it will open it and search for a META-INF folder, and a compiled .class file in a different directory. This other .class file is where we write our malicious script.
I cloned this awesome repo which automatically assembles the payload for SnakeYAML, and set the command to
xcalc, for old times’ sake. I compiled everything, hosted the .jar payload locally using Python, pulled it and voila!
So, with all this in mind, might as well go all the way and write a Python exploit. All we need to do is:
- Generate the YAML file which invokes an HTTP GET request (through ScriptEngineManager) to some URL we control
- Compile a .jar file with a suitable command (like a reverse shell)
- Host that Jar file
- Send the malicious YAML to an Any23 endpoint to trigger the exploit and fetch the malicious jar
There are several ways to send a request to Any23’S RESTful API, I chose the simplest which is a GET request to the target, followed by the desired output format, passing the input document directly into the URL like so:
This causes the server to fetch the YAML document from the attacker (our machine). Inside that YAML is another URL leading to the .jar, which it will follow. As soon as the target fetches the .jar it executes.
When it comes to reverse shells and larger commands executed from Java, I found that creating a Java
Process by calling
Runtime.getRuntime().exec with an array of strings as the command:
Is much more effective than the default approach, which failed to execute more complex commands:
Github link for the PoC code here. More PoC exploits to come!