maptool "wormable" unauthenticated rce
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.
Here, have a video of me running the exploit on my laptop.
Maptool
So what is this maptool thing? Well maptool is a tool to make maps (duh). Specifically it is a tool to create fantasy worlds for tabletop systems like dnd. People create beautiful maps like:
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
unauthenticated rce.
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.
Heccering
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
this class:
|
|
and you have a Deserializer
.
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
Example
- 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 object
byte
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 object
byte, 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.
Entry Points
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 Collection
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.
The 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 entry points
.
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:
- compareTo
- hashCode
- readResolve
- the constructor with the least amount of parameters
- toString
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 classpath
. All
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
interesting 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.
The interesting 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 toString
method.
|
|
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 Iterator
here. Quick programming lesson on iterators and iterables, feel free to skip.
Iterable:
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 iterator
.
Iterator:
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 iterable
. Then
this iterator
gets passed to the IteratorUtils::toString
method.
But let’s start at emptyIteratorIfNull
.
|
|
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 Iterator
.
|
|
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?
|
|
Yepp. This 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 toString
to next
to transform
to 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 Iterator
whos Iterable
we can control.
Quick recap. We need an iterable
whos iterator
method returns a TransformIterator
.
Just doing a quick search
|
|
mmmmmmmmmmmmmm
|
|
mmmmmmmmmmmmmmmmmmmmmm
|
|
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
Yeah maybe this assumption was a bit to optimistic.
The iterators
seem to be rather hardcoded.
But how tf are you actually supposed to use the TransformIterator if there is
no iterator
implementation returning it?.
Well.
|
|
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
not memes
memes
not memes
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 transformedIterable
method:
|
|
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 transformer
and 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 val$transformer
and val$iterable
.
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
toString
yet.
For 1. we can used ChainedTransformer
which is a transformer that runs
multiple transformers (we can control) one after another.
Hessian.toString()
Calling toString
wasn’t that easy. When i made the list of entry points,
i put toString
on there when i saw the following piece of code inside
hessian’s deserialization code(com.caucho.hessian.io.JavaDeserializer.ObjectFieldDeserializer::deserialize
):
|
|
When hessian deserializes the field of an object it will:
- read the fields value by deserializing it into the
value
variable - 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 logDeserializeError
method takes value
as an argument (we have control over value
).
logDeserializeError
:
|
|
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 Map
.
The object will be deserialized into the value
variable but assigning it to
the 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 (conn.readMessage(in)
).
Next they’re dispatching
that message. But what does that even mean?
We ultimately end up in net.rptools.clientserver.hessian.AbstractMethodHandler
.
|
|
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
the server.
Digging deeper into handleMessage
, the first interesting thing that happens
is startCall
.
|
|
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
be 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
discovered.
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:
- get
ClassLoader
instance - use reflection to make the
ClassLoader::defineClass
method publicly accessibe - call
defineClass
with 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
. The 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
as the InvokerTransformer
does. There is however the TransformerClosure
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 InvokerTransformer
.
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
time):
|
|
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 null
for 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.
Resources
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