Waddup my interwebbian people. today were taking a look at an unauthenticated rce in an open-source java project called maptool. To make things worse, it is also wormable in the way that once you can run code on the server; you will also be able to run code on each and every connected client. In this post ill go through my process of writing the exploit while explaining it as best as i can to make this a viable resource on serialization exploits. Sorry if im overexplaining shit i tried to keep this very accessible.
Then you invite your players which get the ability to walk around on this map together, defeating epic encounters. Inviting friends you say? Yepp. You start a maptool server, set a password so only your friends may connect and youre good to go!
So why did i decide to take a look at this open-source project? Thats actually
a funny story because technically i didn’t. You see there is this twitch
streamer who got a pretty big following by dm’ing
for a good amount of rather popular streamers. His name is Arcadum
and i can highly recommend his streams ❤️.
Hes honestly my favorite dm and my favorite stream to watch at the moment but enough jerking off.
I noticed how slow maptool loads maps because every texture is send over the wire one by one in a pretty inefficient way. Sorry maptool people but things are kind slow when not cached.
So i actually just wanted to make this process faster, cloned the repo and found an authenticated vuln by accident.
This authenticated vuln is actually kinda boring and also not the point of this
post so ill spare you the details.
Next i wanted to check if i can bypass the password check to make it an
Didn’t take me long to find
Handshake::receiveHandshake which was the method responsible for checking the clients password.
Lo and behold there it was. Let’s just take a look at the first couple of lines:
Whats Hessian? A serialization library. OOF.
They’re reading the clients inputs with deserialization. In case you didn’t know, this is what OWASP has to say about this.
Data which is untrusted cannot be trusted to be well formed. Malformed data or unexpected data could be used to abuse application logic, deny service, or execute arbitrary code, when deserialized.
Yep. deserialization of untrsted data can lead to arbitrary code execution. And thats exactly what were doing today. But before we delve into the technical shit, lets take a moment to realize what the impact of this problem really is. So first of all, they have a server registry where servers hosted with their tool is listed. Which means we can literally take this registry as a list of ips we can run code on (and all connected clients :p). Like most network programs, they have a default port. So we could run good ol’ masscan, scan for the default port and goto town. Now this is where is gets ridiculous. Remember what i said about who is using this program before. it’s highly targeted streamers like
Just to name a few. There actually is a list just in case you care.
First, lemme give you a TL;DR.
Suppose you make a program that creates an instance from a class. Also known as an object. You allow the user to choose that class; Any class that exists.
Now that program calls
.toString on that object.
toString is a method.
And it is implemented in a different way in each class.
So let’s say we have a class that has a
toString method which runs an evil command.
Creating an object of that class and calling
toString on it is evil right?
The user can control the class that gets used, so we can instruct it to use the evil class.
So depending on what class the object is from, different code gets run when you call
toString on it.
But the class needs to already exist in the target program.
And the target program obviously doesn’t have an evil class.
That’s where the chaining comes into play.
Because maybe there is a class that has a
.toString method that calls
.next on an object of a class we choose. And maybe that
.next method calls
.transform on an object of a class we choose. And maybe that
.transform runs any java method we choose with any arguments we choose.
And maybe there is a java method that runs system commands (there is in every language).
That’s basically what a serialization rce is. If you want all the details, clever tricks and hopefully a learning experience, i invite you to go on; friend.
Now this is the part you probably came here for. So grab your best cup of tea, put on some music and enjoy my endeavour into madness and pwnage. If you already know how serialization works, feel very free to skip this next part as you will gain no new knowledge whatsoever.
Serialization / Deserialization
Let’s make this quick. You have some code known as a
Serializer. You have
and you have a
So lets make an instance of our class and serialize it in some pseudocode.
So with serialization we can turn objects into bytes and those bytes back into objects. But how does it do this? Let’s quickly run down what happens in the serialization process.
- write name of class of input object to output bytes. So in our case
- for each field of the object do:
- write name of field to output bytes
- write value of field to output bytes (this serializes the value of the field so we recursively goto step 1)
- write an
end of objectbyte
The deserialization process:
- read name of class from input bytes
- create an instance of this class (it must have this class in it’s own environment)
- until we reach the
end of objectbyte, do the following:
- read a string from the bytes indicating the field name
- read the value of this field from the input stream (this deserializaes the value of the field so we recursively goto step 1)
- assign the value we just read as the value of the current field in the current object
- return the object we recovered to the caller
This is the very very basic serialization / deserialization process. There is alot more to it as we will soon find out.
So it turns out, most deserializers call a few methods on the deserializied
object after recovering it’s fields (fields = member variables). For example to deserialize a
which is a just any sequence of objects; deserializers usually don’t care about
the collection’s fields (don’t worry this will make sense in a moment).
They will instead make a new
Collection and call
.put on it to add the
collection’s elements one-by-one. This means we can call
put on every collection we like.
put method on a sorted collection will for example call
compareTo on it’s
elements to keep them sorted (now it gets interesting). So we can call
compareTo on any class of our choosing. I will refer to these initial methods
which are called during deserializaion as
Since i was unable to find any existing research of hessian that would help me
in this case (they were all specific to libraries which aren’t used here), i had
to make my own
gadget chain. We will learn what that means later on.
The first thing i did was find
entry points in hessian by going through it’s
serializaion code. To name just a few i found:
- the constructor with the least amount of parameters
Gadgets And Chains
First i started with
compareTo. Remember we can have our target deserialize
any class it
knows about. In java that means it’s in the
classes of a Java program and all of the classes of it’s dependencies are in
it’s classpath. Then i had a good look at the
compareTo methods on a couple of
classes in a couple of dependencies of our target. CompareTo seemed like a bad
target (even tho if i looked longer i probably would have found something) so i
switched over to
toString. It didn’t take long until i found a very
toString implementation in a library they were using called
common-colections4. This library is actually rather known for it’s
deserialization gadgets. we will later discover why that is.
toString implementation i found was a on the class
public class FluentIterable<E> implements Iterable<E>. You might be like
but catnip, this class isn’t even serializable
Yep. It isn’t. Hessian does not check if something is marked as
Serializable when it deserializes it. Hessian refuses to serialize
things which are not marked as serializable. But it doesn’t do this check when
deserializing. Anyways, back to the
Interesting. It passes a field (we can control fields, remember) to
IterableUtils.toString so lets check that out.
It’s important to know the difference between an
Iterable and and
here. Quick programming lesson on iterators and iterables, feel free to skip.
An iterable is just a sequence of elements. Lists, Stacks, Vectors, all your
usual collections classes are iterables. To actually get the elements of the
iterable, it needs to tell us what
iterator to use. It does this by
returning a specific
iterator object in a method also named
An iterator is an object that gets us the next object in a sequence.
We literally call
next on the iterator to get the next element.
But sometimes iterators do something special in addition to returning the next
element. It could for example add +1 to the element and then return it.
There are tons of special iterators that do fancy shit.
So here we are passing our
iterable to the method
emptyIteratorIfNull which will get the
iterator of our
iterator gets passed to the
But let’s start at
The method is getting the
iterator of our
iterable by just calling
.iterator(). For now, let’s assume we can control what
iterator is used
here. It ends up in
IteratorUtils::toString as mentioned before.
Ok so we pass the iterator and add some other hardcoded memes we don’t care about because we cant control them.
We first call
Iterator::hasNext which sounds rather boring (i actually didn’t look into that call :p) and then we call
Iterator::Next. Now thats a good one. Why is the ability to call
Next on any
Iterator “a good one”? Let me introduce you to my favorite
An Iterator that calls
Transformer::transform on a field. Again, we control those so we can call the transform method of any class we want. dope. What transformers are there?
transformer literally calls a function of our choosing with parameters of our choice on an object of our choice. Arbitrary. Code. Execution. Notice how we went from
invoke? Those classes which gets us from place
A to place
B until we reach our final destination are called
Gadgets. And the combination of those gadgets is called a
Gadget Chain. Now we could just do the good old
Runtime.getRuntime().exec and execute system commands. But im gonna do something way cooler. Before we do that however, we need to go back a couple of steps and proof something.
From Iterable To TransformIterator
Earlier in this post i told you to
assume we can control what iterator is used here
But i didn’t actually show you an
Iterable we can control.
Quick recap. We need an
iterator method returns a
Just doing a quick search
Yeah maybe this assumption was a bit to optimistic.
iterators seem to be rather hardcoded.
But how tf are you actually supposed to use the TransformIterator if there is
iterator implementation returning it?.
Okkkkkkkkkkkkkk. They have this method which takes an
iterable and a
transformer and then it does something funny which returns an
iterable with a
TransformIterator. But we obviously can’t call this method. And we also dont have to :). Let me explain what this
funny thing is that the method does. And if you’re a java dev reading this, i know you were cringing when i called it
something funny :p. Java (and many other languages) have a feature called Anonymous Classes. It works like this:
On line 8 we make an instance of the class
SomeClass BUT we override the
toString method with our own implementation. So when calling
toString on this instance (y), it will always return “memes”. We don’t change the original class tho. Every other instance of
SomeClass will still use the original implementation of
toString. So if we run this code, it will print
But theyre all instances of
SomeClass so how does it work? Java actually creates a new class here. The class doesn’t have a name which is why we call it an
anonymous class. OOOOOOOOOOOKKK can we actually take the anonymous class from inside the
and tell hessian to make an instance of it? Keep in mind we also need to control the
transformer variable so we can plug in our
InvokerTransformer. The answer is yes. First of all, anonymous classes do have names. They just dont have names in java. Confused? Lemme explain. For the java virtual machine to be able to create an instance of a class, it needs a name for that class, anonymous classes are no exception. For every anonymous class, java will just make up a name. The naming convention it uses is [class the anonymous class is inside of]$[index of anonymous class]. For example; our anonymous class is in the method
transformedIterable which is inside the class
IterableUtils. it is also the 10th anonymous class. So the name java gives it is
IterableUtils$10. Dope but how do we control the transformer, which is not a member variable but an argument to the method? As a reminder, here is the method again:
We need control over the
transformer variable too. But if we pass a transformer to this method, it just returns an iterable. How does the variable still exist when the method already finished executing? The method returned right? Why would it’s arguments still exist? There is a thing going on here called closure. Because java is smart (debatable); it knows that a method in the anonymous class is using an argument from the enclosing method. Those arguemnts are
iterable. So it makes up some names for those arguments and puts them in the anonymous class as fields. Heh we control those ;). So what does the java compiler name those closure fields? It does
val$[name of variable]. In our case that means we have
Reiterating The Chain
Get it? Reiterating? Because were using iterators? Very funny i know. Im just gonna put the gadget chain here
And just to be crystal clear, here is the payloads object structure in a json like format:
Alright, 2 things:
- Calling a method on a deserialized object inside the hashset (iterable) is easy. But we’ll need to call multiple methods to do interesting shit.
- We don’t know how to actually call
For 1. we can used
ChainedTransformer which is a transformer that runs
multiple transformers (we can control) one after another.
toString wasn’t that easy. When i made the list of entry points,
toString on there when i saw the following piece of code inside
hessian’s deserialization code(
When hessian deserializes the field of an object it will:
- read the fields value by deserializing it into the
- assign this value to the actual field
When this process fails it logs an error (that’s actually where the magic
happens). I noticed the
value as an argument (we have control over
The interesting part is
If the value argument isn’t null, we concatinate it to a string
"(" + value + ")").
Concatination in java calls
toString :). But how do we cause an exception in
a way that doesnt cause
value to be null?
If we get
value = in.readObject(_field.getType()); to run and
_field.set(obj, value); to fail,
we should be good.
Im thinking: pick a random class like
And tell hessian to deserialize it. When it gets to the
map field, it will
deserialize the value and we will give it something that isn’t a
The object will be deserialized into the
value variable but assigning it to
map field will crash because it isn’t a map.
but nipcat, won’t
value = in.readObject(_field.getType());crash?
It looks like it doesn’t it? It does know the objects fields type and passes that to
readObject so there is no reason it would let us choose any class.
@param expectedClass the expected class if the protocol doesn’t supply it.
Thats what their documentation says. A quick look reveals that they only use this class if serializer doesn’t specify it. We can just tell it to use another class.
As we want to provoke the assignment error as our means of calling tostring, we need to serialize a broken object. For that we can just look at the hessian source code (lets go way back ;) and make our own serializer.
Object structure becomes:
Let’s get advanced.
Moving To Clients
In the title i used the word
wormable and now it’s time to address that part. As
serialization is used for the login process, you probably wont be
surprised to hear that serialization is used for alot more things in their
codebase. For their network protocol they have their own repo called
clientserver. In the class
net.rptools.clientserver.simple.client.ClientConnection they handle sending/receiving packets on the client side. When connecting to a server, a special thread to receive data is started. The thread will
They’re reading a message by first reading the size of the message as an int
then reading that many bytes as the message (
dispatching that message. But what does that even mean?
We ultimately end up in
Huh, this is an rpc (remote procedure call). It’s using hessian to receive a
serialized method call then execute it. More serialization ;). Once we can
execute java code on the server, we can get the client connections and send
them a malicious packet which will get deserialized by the above
handleMessage code and boom, we infected all connected clients. How do we run
code on the client? The
handleMethod method only let’s us call selected
methods and not arbitrary ones but that’s fine because the methods arguments are
subject to serialization. The gameplan would be to send serialized arguments to
the client which will lead to code execution in the same way we reached rce on
Digging deeper into
handleMessage, the first interesting thing that happens
First we need to pass
readCall without crashing.
We read a byte and check if it’s
c. Then we read 2 other bytes, combine them
into a short and return it. Back in
startCall we now
readHeader and check
if it returns a non-null value.
Ok tag has to be ‘H’, then we read 2 more bytes and then read bytes as long as
the byte is bigger than 0. We actually dont care about any of these fields so
im gonna put
0 in all of those. The only byte we need is
tag which needs to
H so we dont return
null. Back to
startCall again, we now call
readObject which is all we need to gain code execution as we have already
TL;DR: send a bunch of garbage so we reach
readObject, then send malicious object.
Cool! We know what to send to the client but how do we get the server to send this payload to all the clients. The easiest approach (in my opinion) is to send some compiled java bytecode code to the server and have it execute that. From there we can just write java code that sends the clients our payload. Running compiled java bytecode works like this:
- use reflection to make the
ClassLoader::defineClassmethod publicly accessibe
defineClasswith our compiled bytecode in the form of a bytearray
- use the return value (which is the class we compiled) to call a method on it
- this method is the code we want to execute ;)
Translating this code to
transformers gets rather functional.
First we use a
ChainedTransformer takes a list of
transformers, transforms the first input and passes the transformed input to the next
transformer. So we call a method on an object, call something on the returned
object, call something on that returned object and so on. Cool, we use a
ClassLoader instance as our first input and call
defineClass on it. But wait
we need to make it publicly accessibe first or we cant call it. So we use the
ClassLoader class as our first input and call
getDeclaredMethod('defineClass'...) on it, call
setAccessible(true) on it
and then call the method. But
setAccessible doesnt return anything :(.
Due to the
ChainedTransformer using return values as input to the next
transform we cant do
setAccessible because it returns void, causing the chain
to break. This problem is easily solved by using a
ClosureTransformer. In the
context of common-collections4, a closure is an object that does something with
the input and then returns that input. There are different closures that do
different things with the input but there is none that executes something on it
InvokerTransformer does. There is however the
which runs a transformer on it’s input. There is also the aforementioned
ClosureTransformer which is a transformer that runs a closure. To put all
this in simpler terms: a transformer transforms the input, meaning it returns
the result of an operation. A closure does something on the object, returning
the original object. With this in mind i present:
As promised in the comments above, let’s look into getting an instance of a
ClassLoader. Because the instance will be an argument, we can’t use
transformers or anything fancy to get it. It needs to be deserializable by
hessian in order to put it in the
iArgs field of the
Hessian creates instances by looking for the constructor with the least amount
for arguments. So we are looking for a ClassLoader where the constructor with
the least amount of arguments doesnt throw an exception. I just checked all
classes that extend
ClassLoader with our requirements in mind
until i found
java.security.SecureClassLoader. It has an empty constructor
that does absolutely nothing. Perfect :).
At this point our complete payload is (and im going to be very verbose this
Cool. only one puzzle piece remains.
Stage2 & Stage3
As mentioned in the comments above, stage2 is the java bytecode were running on the server. Im just gonna put my stage2 here and explain it within the comments
Whats stage3 you ask? Stage3 is just stage1 (the initial serialization payload)
except it passes
stage3 when executing stage2 (the memes
function). I know i know, confusing as hell but maybe youll have an easier time
understanding when reading the poc. The POC is in python btw because i really
fucking hate writing in java and i especially hate dealing with dependencies
and installed jdks.
Reporting & Resolving
I saw the maptool team linked their discord server on the website. So i just pinged the admins like
yo we need to talk about something in private, its rather serious
Didn’t take long for an admin to respond and i told them about the situation.
I obviously checked if they had the
admin role and if their username matched
one of the github project owners first.
Anyway, those are the solutions i proposed:
- hessian 4.0 has a whitelist feature
- remove serialization before authentication completely
- long term, maybe consider moving away from serialization completely
They agreed with me, we had a nice chat and they invited me to a private discord server for discussing this with the other members. They are pretty chill and took the issue seriously, which is great. One of the devs actually descibed the situation perfectly with this beautiful gif. If you are reading this, they have fixed the issue and publicly disclosed the event.
For educational purposes i also provided the 2 most used maptool versions for windows. These are both VULNERABLE and should not be used!
2020-07-08 20:45:40 +0200 CEST