From e5d0a43ba42c7023a93e8e33356909e614783ac7 Mon Sep 17 00:00:00 2001 From: tahmid-23 <60953955+tahmid-23@users.noreply.github.com> Date: Mon, 28 Nov 2022 00:13:09 -0500 Subject: [PATCH] add BungeeGuard authentication (#1502) --- .../server/extras/bungee/BungeeCordProxy.java | 43 +++++++++++++- .../client/handshake/HandshakePacket.java | 59 ++++++++++++++++--- 2 files changed, 93 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/minestom/server/extras/bungee/BungeeCordProxy.java b/src/main/java/net/minestom/server/extras/bungee/BungeeCordProxy.java index c4f9f577e..82884558d 100644 --- a/src/main/java/net/minestom/server/extras/bungee/BungeeCordProxy.java +++ b/src/main/java/net/minestom/server/extras/bungee/BungeeCordProxy.java @@ -1,12 +1,22 @@ package net.minestom.server.extras.bungee; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + /** - * BungeeCord forwarding support. This does not count as a security feature, and you will still be required to manage your firewall. + * BungeeCord forwarding support. Enabling BungeeGuard support with {@link #setBungeeGuardTokens(Set)} helps to secure the server, + * but managing your firewall is still recommended. *

* Please consider using {@link net.minestom.server.extras.velocity.VelocityProxy} instead. */ public final class BungeeCordProxy { + private static Set bungeeGuardTokens = null; + private static volatile boolean enabled; /** @@ -24,4 +34,35 @@ public final class BungeeCordProxy { public static boolean isEnabled() { return enabled; } + + /** + * Sets the tokens used by BungeeGuard authentication. + * Setting the tokens to a not-null value enables BungeeGuard authentication, + * and setting it to a null value disables BungeeGuard authentication. + * + * @param tokens The new BungeeGuard authentication tokens + */ + public static void setBungeeGuardTokens(@Nullable Set tokens) { + bungeeGuardTokens = tokens; + } + + /** + * Checks whether BungeeGuard authentication is enabled. + * + * @return Whether BungeeGuard authentication is enabled + */ + public static boolean isBungeeGuardEnabled() { + return bungeeGuardTokens != null; + } + + /** + * Checks whether a token is one of the valid BungeeGuard tokens + * + * @param token The token to test + * @return Whether the token is a valid BungeeGuard token + */ + public static boolean isValidBungeeGuardToken(@NotNull String token) { + return isBungeeGuardEnabled() && bungeeGuardTokens.contains(token); + } + } diff --git a/src/main/java/net/minestom/server/network/packet/client/handshake/HandshakePacket.java b/src/main/java/net/minestom/server/network/packet/client/handshake/HandshakePacket.java index e24d2a8f0..e8922654e 100644 --- a/src/main/java/net/minestom/server/network/packet/client/handshake/HandshakePacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/handshake/HandshakePacket.java @@ -16,6 +16,8 @@ import net.minestom.server.network.player.GameProfile; import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.network.player.PlayerSocketConnection; import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.net.SocketAddress; import java.util.ArrayList; @@ -27,14 +29,20 @@ import static net.minestom.server.network.NetworkBuffer.*; public record HandshakePacket(int protocolVersion, @NotNull String serverAddress, int serverPort, int nextState) implements ClientPreplayPacket { + private final static Logger LOGGER = LoggerFactory.getLogger(HandshakePacket.class); + /** * Text sent if a player tries to connect with an invalid version of the client */ private static final Component INVALID_VERSION_TEXT = Component.text("Invalid Version, please use " + MinecraftServer.VERSION_NAME, NamedTextColor.RED); - private static final Component INVALID_BUNGEE_FORWARDING = Component.text("If you wish to use IP forwarding, please enable it in your BungeeCord config as well!", NamedTextColor.RED); + + /** + * Indicates that a BungeeGuard authentication was invalid due to missing, multiple, or invalid tokens. + */ + private static final Component INVALID_BUNGEE_FORWARDING = Component.text("Invalid connection, please connect through the BungeeCord proxy. If you believe this is an error, contact a server administrator.", NamedTextColor.RED); public HandshakePacket { - if (serverAddress.length() > (BungeeCordProxy.isEnabled() ? Short.MAX_VALUE : 255)) { + if (serverAddress.length() > getMaxHandshakeLength()) { throw new IllegalArgumentException("Server address too long: " + serverAddress.length()); } } @@ -47,7 +55,7 @@ public record HandshakePacket(int protocolVersion, @NotNull String serverAddress @Override public void write(@NotNull NetworkBuffer writer) { writer.write(VAR_INT, protocolVersion); - int maxLength = BungeeCordProxy.isEnabled() ? Short.MAX_VALUE : 255; + int maxLength = getMaxHandshakeLength(); if (serverAddress.length() > maxLength) { throw new IllegalArgumentException("serverAddress is " + serverAddress.length() + " characters long, maximum allowed is " + maxLength); } @@ -64,6 +72,12 @@ public record HandshakePacket(int protocolVersion, @NotNull String serverAddress final String[] split = address.split("\00"); if (split.length == 3 || split.length == 4) { + boolean hasProperties = split.length == 4; + if (BungeeCordProxy.isBungeeGuardEnabled() && !hasProperties) { + bungeeDisconnect(socketConnection); + return; + } + address = split[0]; final SocketAddress socketAddress = new java.net.InetSocketAddress(split[1], @@ -78,7 +92,8 @@ public record HandshakePacket(int protocolVersion, @NotNull String serverAddress ); List properties = new ArrayList<>(); - if (split.length == 4) { + if (hasProperties) { + boolean foundBungeeGuardToken = false; final String rawPropertyJson = split[3]; final JsonArray propertyJson = JsonParser.parseString(rawPropertyJson).getAsJsonArray(); for (JsonElement element : propertyJson) { @@ -92,15 +107,28 @@ public record HandshakePacket(int protocolVersion, @NotNull String serverAddress final String valueString = value.getAsString(); final String signatureString = signature == null ? null : signature.getAsString(); + if (BungeeCordProxy.isBungeeGuardEnabled() && nameString.equals("bungeeguard-token")) { + if (foundBungeeGuardToken || !BungeeCordProxy.isValidBungeeGuardToken(valueString)) { + bungeeDisconnect(socketConnection); + return; + } + + foundBungeeGuardToken = true; + } + properties.add(new GameProfile.Property(nameString, valueString, signatureString)); } + + if (BungeeCordProxy.isBungeeGuardEnabled() && !foundBungeeGuardToken) { + bungeeDisconnect(socketConnection); + return; + } } final GameProfile gameProfile = new GameProfile(playerUuid, "test", properties); socketConnection.UNSAFE_setProfile(gameProfile); } else { - socketConnection.sendPacket(new LoginDisconnectPacket(INVALID_BUNGEE_FORWARDING)); - socketConnection.disconnect(); + bungeeDisconnect(socketConnection); return; } } @@ -117,8 +145,7 @@ public record HandshakePacket(int protocolVersion, @NotNull String serverAddress connection.setConnectionState(ConnectionState.LOGIN); } else { // Incorrect client version - connection.sendPacket(new LoginDisconnectPacket(INVALID_VERSION_TEXT)); - connection.disconnect(); + disconnect(connection, INVALID_VERSION_TEXT); } } default -> { @@ -126,4 +153,20 @@ public record HandshakePacket(int protocolVersion, @NotNull String serverAddress } } } + + private static int getMaxHandshakeLength() { + // BungeeGuard limits handshake length to 2500 characters, while vanilla limits it to 255 + return BungeeCordProxy.isEnabled() ? (BungeeCordProxy.isBungeeGuardEnabled() ? 2500 : Short.MAX_VALUE) : 255; + } + + private void disconnect(@NotNull PlayerConnection connection, @NotNull Component reason) { + connection.sendPacket(new LoginDisconnectPacket(reason)); + connection.disconnect(); + } + + private void bungeeDisconnect(@NotNull PlayerConnection connection) { + LOGGER.warn("{} tried to log in without valid BungeeGuard forwarding information.", connection.getIdentifier()); + disconnect(connection, INVALID_BUNGEE_FORWARDING); + } + }