Dev.to Security πŸ” Cybersecurity πŸ‘ 0 πŸ“– 3 min read

Letting strangers ping any Minecraft server without turning our backend into an SSRF tool

We added a free page to MineOps where anyone can type a Minecraft server address and see what the multiplayer list would show: online or not, version, players and response time. No account. The feature is a few lines. T

We added a free page to MineOps where anyone can type a Minecraft server address and see what the multiplayer list would show: online or not, version, players and response time. No account.

The feature is a few lines. The risk is the interesting part: our server now opens a TCP or UDP connection to whatever address a stranger types. Done naively, that's a server-side request forgery (SSRF) tool pointed at our own network, our cloud provider's metadata service and everyone's databases.

Here's what the check does before it connects to anything.

1. Every DNS answer must be public

A name can resolve to several addresses. If even one is private, we refuse the whole name, so a record can't mix a real server with an internal one.

async def resolve_public(host: str) -> str:
    answers = await loop.getaddrinfo(host, None, type=socket.SOCK_STREAM)
    addresses = list(dict.fromkeys(str(a[4][0]) for a in answers))
    if any(public(address) is None for address in addresses):
        raise Refused("That name leads to a private network, so it can't be checked from here.")
    return addresses[0]

public() leans on Python's ipaddress module, plus one trap it doesn't cover for you: IPv4 hidden inside IPv6.

def public(value: str):
    address = ipaddress.ip_address(value.split("%", 1)[0])
    if isinstance(address, ipaddress.IPv6Address):
        inner = address.ipv4_mapped or address.sixtofour or (address.teredo or (None, None))[1]
        if inner is not None and public(str(inner)) is None:
            return None
    if not address.is_global or address.is_multicast or address.is_reserved:
        return None
    return address

is_global already rules out private ranges, loopback, link-local (including 169.254.169.254, the metadata address) and the shared 100.64.0.0/10 carrier-grade NAT range. Without the IPv6 unwrapping, ::ffff:10.0.0.5 would slip past.

2. Connect to the address you checked, not the name

If you check a name and then hand the name to your socket library, it resolves again. A DNS server with a short TTL can answer "public" the first time and "internal" the second. That's DNS rebinding. So the check resolves once and connects to that exact IP. The name only goes into the Minecraft handshake, where the game itself puts it.

3. Follow SRV records, under the same rules

Java players often type a bare domain, and the game looks up a _minecraft._tcp SRV record to find the real host and port. We do the same, and the record's target and port go through the same checks as a typed address. Otherwise an SRV record becomes a way around them.

4. Only ports a game server would use

Ports below 1024 are refused, and so are well-known database, cache, queue, container and admin ports. Names that only mean something on a home network (.local, .lan, .internal, .home.arpa and friends) are refused before any lookup.

5. Say only a client's first words

The check sends what a Minecraft client sends to list a server, and nothing else: Java's status request or Bedrock's unconnected ping. It reads at most 128 KiB, with 4-second timeouts.

6. Count before connecting

Limits are checked before any socket opens: 10 checks a minute and 60 an hour per visitor, 12 a minute per target, and 600 a minute for everyone together. That's plenty for a person and useless for a scan.

What's left

A public game server will see our address in its connection log, and a server that hides its status shows as reachable with its status hidden. Both are fine for a status checker.

If you build anything that connects to user-supplied addresses, a webhook tester, a link previewer or an uptime checker, the same list applies: check every answer, connect to what you checked, apply the rules to redirects and SRV records too, and rate limit before the socket.

The checker is at mineopsdash.com/minecraft-server-status. It's part of MineOps, which watches Minecraft servers you host on your own Linux or Mac machine and alerts your Discord when one crashes, lags or fills its disk.

Not an official Minecraft product. Not approved by or associated with Mojang or Microsoft.

πŸ“° Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.