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);
+ }
+
}