Fix a major design flaw in the game's translatable component system
Minecraft's translatable text component has a major design flaw which allows a malicious player or server to crash both clients and servers and exhaust their system resources using a specially-crafted translatable text component, which can be compressed down into a 1,056 byte payload. At best, this crashes players and boots them back to the launcher after seeing an entity with a funky name and at worst it can cause worlds to become completely unusable and even prevent people from playing on multiplayer.
I am creating this feature request because I am exhausting every means I have to get this exploit fixed. Mojang is aware of the exploit but has not fixed it for a few years now. Paper's developers deleted private and public exploit reports regarding the exploit. Adventure's developers supposedly fixed the exploit in their implementation but I have yet to see if it actually made a difference. Though, Spigot fixed the exploit pretty quickly after it was reported to their team.
This exploit affects versions 1.7.2 all the way up to 1.21.10, but wasn't known about or circulated in exploit circles until its initial discovery in September 2022. More concerningly however is a new variant capable of effectively banning players from playing multiplayer without being on a server. I am now creating this feature request with the hope that someone smarter than me could develop patches for the exploit which would protect users of NeoForge from malicious servers and players. I strongly urge you to consider patching this.
The Exploit
Translatable text components can be nested an unlimited amount of times, and when this happens it's possible to create a small text component that produces an exponentially larger output during the component visiting process. The game accepts user-created text components in several places including (but not limited to) chat messages, teams, scoreboards, action bars, entity names, sign text, title messages, and more concerningly server MOTDs, making the attack surface for this exploit massive.
A typical payload looks like this, but can be easily tweaked and scaled up or down to suit the exploiter's needs.
{"translate":"%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s","with":[{"translate":"%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s","with":[{"translate":"%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s","with":[{"translate":"%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s","with":[{"text":"ouch!"}]}]}]}]}
When the game goes to visit the translatable text component that payload generates, it generates a string so massive it crashes pretty much everything it gets thrown at. Clients instantly freeze and begin to exhaust memory and CPU resources and servers lock up and do the same (depending on how it was deployed). Some variants are a bit more merciful and just cause the game to quickly hit the "out of memory!" screen, but others aren't so lucky.
Multiplayer ban exploit?
More concerningly is the fact that the exploit can be applied to the MOTD of a server, meaning any client that pings a malicious server will instantly freeze/crash the moment it gets a response, effectively preventing them from playing multiplayer. Furthermore, it takes up large amounts of system resources while it's frozen, potentially causing system-wide slowdowns in some cases. Linked below is a YouTube video I recorded demonstrating this variant.
https://www.youtube.com/watch?v=HiY14SoLZOE
I'm not a maintainer, but I am curious and want to clarify something. How is this a "multiplayer ban exploit"? Even if a client loads a malicious MOTD and hangs/crashes, and we assume that the MOTD is not changed, the user can simply disable internet (and subsequently remove the appropriate server entry) or delete the servers.dat file before starting the client again.
So that its easier to understand what is happening here, could you get a stack dump or two of a client that is currently trying to decompose a payload like this, specifically in a modern (1.21+) NeoForge version so that the stack is mapped properly.
To get a stack dump you need a JDK installed for the jstack tool. The JDK also has a jps tool to find the process id. The same commands work on all operating systems. Run jps -l (thats lowercase L, not 1) to find the process id of the minecraft client (or get it via task manager), then run jstack <process id> > stack.txt to output the stack dump to a stack.txt file which you can share (use a Gist or similar). Note you need to run jstack while its trying to parse the component, which may be difficult with the system resources issue but shouldn't be impossible.
Here are three dumps from the same running instance.
certainly.txt dump1.txt ouch.txt
I'm not a maintainer, but I am curious and want to clarify something. How is this a "multiplayer ban exploit"? Even if a client loads a malicious MOTD and hangs/crashes, and we assume that the MOTD is not changed, the user can simply disable internet (and subsequently remove the appropriate server entry) or delete the
servers.datfile before starting the client again.
I consider it a multiplayer ban exploit because while there are ways to work around it, the fact remains that it is capable of preventing someone from accessing the multiplayer menu without immediately crashing while still connected to the internet. The average person wouldn't be able to determine which server is causing the crash when the game is offline because all servers in their server list would be reported as offline. Furthermore, they wouldn't think to delete their servers.dat file or nuke the entry manually from an NBT file editor. They'd probably just reinstall the game first.
Though, Spigot fixed the exploit pretty quickly after it was reported to their team.
@VideoGameSmash12 do you have a link to the issue report made to their team? Or when it was fixed? We would like to see how Spigot fixed it
@VideoGameSmash12 Also how exactly did you setup the server to reproduce the issue on client multiplayer screen? I have been trying to reproduce it and cannot succeed. If I use your string example and nest it just under the string limit as the motd entry in server properties file, client can still display the string without issue. If I add code on server side to make the components themselves be nested even further, the codec converts to string and blows up server due to over string limits
I am testing on 1.21.10 NeoForge client and server
Though, Spigot fixed the exploit pretty quickly after it was reported to their team.
@VideoGameSmash12 do you have a link to the issue report made to their team? Or when it was fixed? We would like to see how Spigot fixed it
@TelepathicGrunt Here's a commit that fixed the problem on the server-side from my testing.
https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/commits/e74f0d2d704e9738984cbdc50e5fdfa52b325123#nms-patches%2Fnet%2Fminecraft%2Fnetwork%2Fchat%2FIChatFormatted.patch
@VideoGameSmash12 Also how exactly did you setup the server to reproduce the issue on client multiplayer screen? I have been trying to reproduce it and cannot succeed. If I use your string example and nest it just under the string limit as the motd entry in server properties file, client can still display the string without issue. If I add code on server side to make the components themselves be nested even further, the codec converts to string and blows up server due to over string limits
I am testing on 1.21.10 NeoForge client and server
The way the exploit works is that the contains a text component that is nested within layers of translatable text components. A bruteforce method such as yours doesn't work as it causes it to trip over itself trying to encode the text component in the packet. The beauty of the exploit comes from the small size of the payload compared to the output it becomes, and the idea is that the component becomes exponentially larger the more you nest it.
You need to nest it programmatically using something like this (borrowed from a Paper project which uses Adventure, but it's the same idea):
Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.translatable("%1$s%1$s%1$s", "%1$s%1$s%1$s", Component.text("ouch!"))))))))))))))))))))))));
@VideoGameSmash12 the picture I showed was doing it programmatically. It will produce a component with child components set up exactly the way you showed. Except it doesn’t break client in my testing.
Again, if nested too much that it goes over string limit, the codec that would serialize the competing to a string for the packet will explode on server so client won’t ever see packet. They would just see server refusing to connect without client breaking. And if barely nested enough to be under the string limit, the server sends the packet but client reads it without issue and doesn’t even lag. Memory seemed fine.
This is again, running neoforge in the dev environment so I can launch client and server and then added localhost to client multiplayer screen so server shows up with motd message.
You can clone the neoforge repo locally and play around with it like I did. I cannot reproduce the issue
@VideoGameSmash12 the picture I showed was doing it programmatically. It will produce a component with child components set up exactly the way you showed. Except it doesn’t break client in my testing.
@TelepathicGrunt Can I see the full code you're using to test this? The code I'm seeing in your screenshot shows you creating a large translatable component and then appending similarly large components on top of the original component. This is fundamentally different from my implementation where it creates a translatable component with a large amount of placeholders and then passes another translatable component with the same arguments as the first and so on and so forth until it's decently nested enough.
I see. I had it setup wrong then. Doing the translation replacement nesting like you said, looped 250 times while doing "%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s" still lets client connect just fine. No issue on multiplayer screen too.
If I increase it to "%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s" per loop, the server's codec explodes again due to string too large so client doesn't see the MOTD message. Attempting to connect will disconnect from server saying stackover flow but the multiplayer menu still shows up just fine
Still not able to reproduce