18 comments

[ 6.7 ms ] story [ 58.9 ms ] thread
I don't know anything about the Minecraft code, but can't people just run the code with a serialization filter?

This is available as a backport all the way back to Java 8 and is just a command line option.

https://debugagent.com/java-serialization-filtering-prevent-...

I guess for the same reason many mods have lousy performance.

They are written by people that kind of dabble in Java, not people that are actually knowledgeable in the ways of Java and the JVM.

Also many are written by very new programmers. There’s a Minecraft mod out there that I wrote in my second semester of college for a Minecraft server with at the time hundreds to a thousand concurrent players. As far as I could tell all the other mods on the sever were also written by college students. Talented college students but still. As far as I know it’s still bouncing around in the open source world and being used, but I definitely didn’t know what I was doing then and it is by far the largest bit of Java I’ve written
I would say it’s more the fact that many are written by people very new to programming as a whole. Lots of spaghetti.
K so unsafe code can run inside of the JVM that runs Minecraft.

I assume that the user can then just kill his Minecraft session which solves the client side problem but that leaves Minecraft multiplayer servers vulnerable to exploitation.

Am I missing anyrhing else here? Apologies, I'm stupid.

Code execution in a JVM is typically equivalent to code execution on the host where it's running.

Because this vulnerability affects both clients and servers, it sounds like a good way for someone to write a worm that ends up creating a giant botnet by spreading from servers to clients and vice-versa.

Wow. How have I not realized this. Imagine something like this on vanilla. The implications would be incredible. From 1 player to the server to every client AND THEN to every other server the clients join and so on. I can imagine that only taking days for a majority takeover.

Something like the Samy worn

Or just imagine it in OptiFine which for a long time every sane "vanilla" player used. Yes Java can be decompiled, but on top OF is closed source. At least Sodium and friends are better now.
Well, java also has file i/o APIs. Therefore you can just persist the exploit with a simple rootkit, a systemd service or whatever floats your boat.

It's an RCE after all. A VM doesn't imply security policies of any kind, and by default, nobody uses firejail or similar to isolate what the programs are able to affect on their systems.

Well, yes, all software RCEs can be avoided by turning off the software.

With this, people were able to send arbitrary code to a connected server. They could then make the server send arbitrary code down to every connected client. The infected clients could snoop for various data, and then do the same thing in reverse, returning the data to the devious user.

From a safety point of view the JVM essentially only guarantees type and memory safety. To that end it is just a mechanism to provide the same guarantees any other safe language does (Haskell, c#, rust, etc).

So what this means is an attacker theoretically cannot corrupt your program while it is running. If your program has a path where it can do dangerous stuff, and an attacker can make your program do it, then it will.

The problem hit here is that Java (and many languages of the era) provide a built in object serialiAtion and deserialization mechanism in which the data to be deserialized specifies what object is being instantiated (at one point I think you could literally include Java classes themselves, but I don’t know if that’s relevant here). Now imagine you had a CommandExecutor class the runs a binary when you construct it: Java’s deserializer will happily build it, even if you never intended that to happen.

Modern deserialization libraries require you to specify at each step exactly what you intend to deserialze. Older libraries with bincompat issues takes steps to try and limit the default behavior (objc has NSSecureCoding), which is basically a flag on the class saying “it’s reasonable to load me in an untrusted context” - not the best but better than nothing.

Minecraft Java Edition usually runs at user permissions without sandboxing. These permissions carry forward into Java mods - modloaders generally do not attempt to further lock down mods they load, because that's contrary to the spirit of game modification. So if you get pwned by this, it's game over. All your personal information is compromised and the attacker will be able to remain persistent in all sorts of very banal ways.

Most Minecraft servers run in virtual machines. If the server only handles Minecraft then the worst the attacker can do is use the server to run further attacks elsewhere. That will get you a nasty call from your host's abuse desk[0] but that damage can be contained by rebuilding the server from known-good backups[1] and updated versions of the mods you're running.

Ironically the one environment that wouldn't be immediately pwnable with this would be Pojav Launcher, which is specifically designed to load Java Edition onto iPads using developer debugging permissions. At worst it'd be a beachhead for further attempts to jailbreak your iPad without your knowledge. But that's generally a little too high risk for "turning kids playing Minceraft into a botnet".

[0] If you're lucky, it's the EC2 abuse desk. If you're unlucky, it's the GCP abuse desk.

[1] If you don't have backups you MIIIIGHT be able to get away with copying the world file as long as you only ever load it on a patched Minecraft server and there isn't some kind of persistence vuln in the NBT handling code of Minecraft.

Does anyone keep track of whether nullable references or Java serialization was the more expensive mistake in the long run?
Since null pointers (or references in e.g. Java) triggers segfaults or exceptions I would say it is free test coverage.

Nullable pointers are essentially an "option type" with runtime checks.

There is nothing inherently wrong with Java serialization. It is effectiverly a convoluted way to call eval().

Is eval() a mistake?

If the use of `eval()` isn't plainly obvious (even to someone who's inexperienced in Java), then yes, it's a mistake.

I shouldn't have to read the docs to find out that a seemingly innocent operation is in fact extremely dangerous.

> There is nothing inherently wrong with Java serialization.

I would argue that it's a footgun for the vast majority of developers when a deserialization mechanism:

A) Does something other than restore the exact logical state of the object that was serialized. i.e. can result in arbitrary code execution. This seems hard to entirely exclude from a complex language, but the less likely the language makes it, the more intuitive and safe the language will be for most developers IMO.

B) Defaults to deserializing any type of object, as opposed to using an allowlist model. This is such a gaping hole that I'm surprised Oracle hasn't deprecated deserialization without a stream-specific filter, or at least put it behind an --allow-wildly-insecure-deserialization-behaviour-this-is-a-terrible-idea kind of flag.

> Is eval() a mistake?

Building a language that encouraged developers to exchange messages or store/retrieve data from persistent storage in the form of code that would be passed to eval() would be a mistake.

Eval() in js is not a security hole (at least not in the RCE sense). Eval of arbitrary data from an untrusted source in an environment where “arbitrary execution in the host environment means arbitrary code execution with user privileges” is.

That said arbitrary code execution as part of object deserialization has been a known severe attack vector since the 90s, and the fact that Java still allows it by default without massive restrictions is fairly appalling.