fix(gameclients): bound login session inputs

This commit is contained in:
simoleo89
2026-06-16 21:44:10 +02:00
parent 416d0bb088
commit a37de4556b
5 changed files with 68 additions and 7 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();
@@ -72,7 +72,13 @@ public class SecureLoginEvent extends MessageHandler {
return;
}
String sso = this.packet.readString().replace(" ", "");
String sso = SecureLoginInputGuard.normalizeSsoTicket(this.packet.readString());
if (!SecureLoginInputGuard.isValidSsoTicket(sso)) {
Emulator.getGameServer().getGameClientManager().disposeClient(this.client);
LOGGER.debug("Client is trying to connect with an invalid SSO ticket! Closed connection...");
return;
}
if (Emulator.getPluginManager().fireEvent(new SSOAuthenticationEvent(sso)).isCancelled()) {
Emulator.getGameServer().getGameClientManager().disposeClient(this.client);
@@ -80,12 +86,6 @@ public class SecureLoginEvent extends MessageHandler {
return;
}
if (sso.isEmpty()) {
Emulator.getGameServer().getGameClientManager().disposeClient(this.client);
LOGGER.debug("Client is trying to connect without SSO ticket! Closed connection...");
return;
}
if (this.client.getHabbo() == null) {
// Store SSO ticket on client for grace period tracking
this.client.setSsoTicket(sso);
@@ -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)));
}
}