Merge pull request #232 from simoleo89/fix/gameclients-inputs

fix(gameclients): bound login session inputs
This commit is contained in:
DuckieTM
2026-06-17 10:02:42 +02:00
committed by GitHub
5 changed files with 64 additions and 3 deletions
@@ -14,6 +14,7 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
public class GameClient {
@@ -24,6 +25,7 @@ public class GameClient {
private final LatencyTracker latencyTracker;
private Habbo habbo;
private final AtomicBoolean disposed = new AtomicBoolean(false);
private boolean handshakeFinished;
private String machineId = "";
private String ssoTicket = "";
@@ -153,6 +155,10 @@ public class GameClient {
}
public void dispose(boolean allowSessionResume) {
if (!this.disposed.compareAndSet(false, true)) {
return;
}
try {
this.channel.close();
@@ -73,11 +73,11 @@ public class SecureLoginEvent extends MessageHandler {
return;
}
String sso = this.packet.readString().replace(" ", "");
String sso = SecureLoginInputGuard.normalizeSsoTicket(this.packet.readString());
if (Emulator.getPluginManager().fireEvent(new SSOAuthenticationEvent(sso)).isCancelled()) {
if (!SecureLoginInputGuard.isValidSsoTicket(sso)) {
Emulator.getGameServer().getGameClientManager().disposeClient(this.client);
LOGGER.info("SSO Authentication is cancelled by a plugin. Closed connection...");
LOGGER.debug("Client is trying to connect with an invalid SSO ticket! Closed connection...");
return;
}
@@ -0,0 +1,20 @@
package com.eu.habbo.messages.incoming.handshake;
final class SecureLoginInputGuard {
static final int MAX_SSO_TICKET_LENGTH = 512;
private SecureLoginInputGuard() {
}
static String normalizeSsoTicket(String ticket) {
if (ticket == null) {
return "";
}
return ticket.replace(" ", "");
}
static boolean isValidSsoTicket(String ticket) {
return ticket != null && !ticket.isEmpty() && ticket.length() <= MAX_SSO_TICKET_LENGTH;
}
}
@@ -3,6 +3,7 @@ package com.eu.habbo.habbohotel.gameclients;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertTrue;
class GameClientManagerContractTest {
@@ -19,4 +20,11 @@ class GameClientManagerContractTest {
assertDoesNotThrow(() -> manager.disposeClient(null));
assertDoesNotThrow(() -> manager.forceDisposeClient(null));
}
@Test
void gameClientDisposeIsExplicitlyIdempotent() throws Exception {
assertTrue(java.util.concurrent.atomic.AtomicBoolean.class.isAssignableFrom(
GameClient.class.getDeclaredField("disposed").getType()
));
}
}
@@ -0,0 +1,27 @@
package com.eu.habbo.messages.incoming.handshake;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class SecureLoginInputGuardTest {
@Test
void normalizesNullAndSpacesBeforeAuthentication() {
assertEquals("", SecureLoginInputGuard.normalizeSsoTicket(null));
assertEquals("abc123", SecureLoginInputGuard.normalizeSsoTicket(" abc 123 "));
}
@Test
void rejectsMissingOrOversizedTickets() {
assertFalse(SecureLoginInputGuard.isValidSsoTicket(""));
assertFalse(SecureLoginInputGuard.isValidSsoTicket("x".repeat(SecureLoginInputGuard.MAX_SSO_TICKET_LENGTH + 1)));
}
@Test
void acceptsTicketWithinBound() {
assertTrue(SecureLoginInputGuard.isValidSsoTicket("x".repeat(SecureLoginInputGuard.MAX_SSO_TICKET_LENGTH)));
}
}