diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/RequestProfileFriendsEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/RequestProfileFriendsEvent.java index cc6a0198..29f51dcf 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/RequestProfileFriendsEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/RequestProfileFriendsEvent.java @@ -10,6 +10,10 @@ public class RequestProfileFriendsEvent extends MessageHandler { @Override public void handle() throws Exception { int userId = this.packet.readInt(); + + if (!UserInputGuard.isPositiveId(userId)) + return; + Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId); if (habbo != null) diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/RequestUserProfileEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/RequestUserProfileEvent.java index e95b7e70..80dd5f57 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/RequestUserProfileEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/RequestUserProfileEvent.java @@ -10,6 +10,10 @@ public class RequestUserProfileEvent extends MessageHandler { @Override public void handle() throws Exception { int habboId = this.packet.readInt(); + + if (!UserInputGuard.isPositiveId(habboId)) + return; + Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(habboId); if (habbo != null) diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/RequestWearingBadgesEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/RequestWearingBadgesEvent.java index ade93a20..bd0a0ba2 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/RequestWearingBadgesEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/RequestWearingBadgesEvent.java @@ -10,6 +10,10 @@ public class RequestWearingBadgesEvent extends MessageHandler { @Override public void handle() throws Exception { int userId = this.packet.readInt(); + + if (!UserInputGuard.isPositiveId(userId)) + return; + Habbo habbo = Emulator.getGameServer().getGameClientManager().getHabbo(userId); if (habbo == null || habbo.getHabboInfo() == null || habbo.getInventory() == null || habbo.getInventory().getBadgesComponent() == null) diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/SaveMottoEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/SaveMottoEvent.java index d3f4322f..9074f2e8 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/SaveMottoEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/SaveMottoEvent.java @@ -9,15 +9,16 @@ import com.eu.habbo.plugin.events.users.UserSavedMottoEvent; public class SaveMottoEvent extends MessageHandler { @Override public void handle() throws Exception { - String motto = Emulator.getGameEnvironment().getWordFilter().filter(this.packet.readString(), this.client.getHabbo()); + String motto = UserInputGuard.normalizeText(Emulator.getGameEnvironment().getWordFilter().filter(this.packet.readString(), this.client.getHabbo())); UserSavedMottoEvent event = new UserSavedMottoEvent(this.client.getHabbo(), this.client.getHabbo().getHabboInfo().getMotto(), motto); Emulator.getPluginManager().fireEvent(event); - motto = event.newMotto; - - if(motto.length() <= Emulator.getConfig().getInt("motto.max_length", 38)) { - this.client.getHabbo().getHabboInfo().setMotto(motto); - this.client.getHabbo().getHabboInfo().run(); - } + motto = UserInputGuard.normalizeText(event.newMotto); + + if (motto.length() > Emulator.getConfig().getInt("motto.max_length", 38)) + return; + + this.client.getHabbo().getHabboInfo().setMotto(motto); + this.client.getHabbo().getHabboInfo().run(); if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != null) { this.client.getHabbo().getHabboInfo().getCurrentRoom().sendComposer(new RoomUserDataComposer(this.client.getHabbo()).compose()); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/SaveUserVolumesEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/SaveUserVolumesEvent.java index ff50cb9d..66d83eaa 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/SaveUserVolumesEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/SaveUserVolumesEvent.java @@ -7,9 +7,9 @@ import com.eu.habbo.plugin.events.users.UserSavedSettingsEvent; public class SaveUserVolumesEvent extends MessageHandler { @Override public void handle() throws Exception { - int system = this.packet.readInt(); - int furni = this.packet.readInt(); - int trax = this.packet.readInt(); + int system = UserInputGuard.clampVolume(this.packet.readInt()); + int furni = UserInputGuard.clampVolume(this.packet.readInt()); + int trax = UserInputGuard.clampVolume(this.packet.readInt()); this.client.getHabbo().getHabboStats().volumeSystem = system; this.client.getHabbo().getHabboStats().volumeFurni = furni; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/UpdateUIFlagsEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/UpdateUIFlagsEvent.java index 8983b29e..79b37475 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/UpdateUIFlagsEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/UpdateUIFlagsEvent.java @@ -5,7 +5,7 @@ import com.eu.habbo.messages.incoming.MessageHandler; public class UpdateUIFlagsEvent extends MessageHandler { @Override public void handle() throws Exception { - int flags = this.packet.readInt(); + int flags = UserInputGuard.sanitizeUiFlags(this.packet.readInt()); this.client.getHabbo().getHabboStats().uiFlags = flags; this.client.getHabbo().getHabboStats().run(); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/UserInputGuard.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/UserInputGuard.java new file mode 100644 index 00000000..2c2857fa --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/users/UserInputGuard.java @@ -0,0 +1,26 @@ +package com.eu.habbo.messages.incoming.users; + +final class UserInputGuard { + static final int MIN_VOLUME = 0; + static final int MAX_VOLUME = 100; + static final int MAX_UI_FLAGS = 0xFFFF; + + private UserInputGuard() { + } + + static boolean isPositiveId(int id) { + return id > 0; + } + + static int clampVolume(int volume) { + return Math.max(MIN_VOLUME, Math.min(MAX_VOLUME, volume)); + } + + static int sanitizeUiFlags(int flags) { + return flags < 0 ? 0 : flags & MAX_UI_FLAGS; + } + + static String normalizeText(String value) { + return value == null ? "" : value.trim(); + } +} diff --git a/Emulator/src/test/java/com/eu/habbo/messages/incoming/users/UserInputGuardContractTest.java b/Emulator/src/test/java/com/eu/habbo/messages/incoming/users/UserInputGuardContractTest.java new file mode 100644 index 00000000..6f44a158 --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/messages/incoming/users/UserInputGuardContractTest.java @@ -0,0 +1,64 @@ +package com.eu.habbo.messages.incoming.users; + +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class UserInputGuardContractTest { + private static String source(String name) throws Exception { + return Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/users/" + name + ".java")); + } + + @Test + void userProfileLookupsRejectInvalidIdsBeforeOfflineQueries() throws Exception { + for (String handler : new String[]{"RequestUserProfileEvent", "RequestProfileFriendsEvent", "RequestWearingBadgesEvent"}) { + String source = source(handler); + int read = source.indexOf("this.packet.readInt()"); + int guard = source.indexOf("UserInputGuard.isPositiveId", read); + int lookup = Math.max(source.indexOf("getOfflineHabboInfo", guard), source.indexOf("getBadgesOfflineHabbo", guard)); + + assertTrue(guard > read, handler + " should validate the packet id after reading it"); + assertTrue(lookup == -1 || guard < lookup, handler + " should validate ids before offline lookups"); + } + } + + @Test + void settingsInputsAreNormalizedBeforePersistence() throws Exception { + String volumes = source("SaveUserVolumesEvent"); + String flags = source("UpdateUIFlagsEvent"); + + assertTrue(volumes.contains("UserInputGuard.clampVolume(this.packet.readInt())"), + "volume settings should be clamped to the client-supported range"); + assertTrue(flags.contains("UserInputGuard.sanitizeUiFlags(this.packet.readInt())"), + "UI flags should be sanitized before persistence"); + } + + @Test + void mottoIsNormalizedAndRejectedBeforeSaveSideEffects() throws Exception { + String motto = source("SaveMottoEvent"); + + int pluginValue = motto.indexOf("UserInputGuard.normalizeText(event.newMotto)"); + int lengthGuard = motto.indexOf("motto.length() > Emulator.getConfig().getInt", pluginValue); + int save = motto.indexOf("setMotto(motto)", lengthGuard); + int achievement = motto.indexOf("AchievementManager.progressAchievement", save); + + assertTrue(pluginValue > -1, "plugin-mutated motto should be normalized"); + assertTrue(lengthGuard > pluginValue && lengthGuard < save, + "motto length should be validated before saving"); + assertTrue(save < achievement, + "motto achievement should only progress after a valid save"); + } + + @Test + void helperClampsAndMasksValues() { + assertEquals(0, UserInputGuard.clampVolume(-20)); + assertEquals(40, UserInputGuard.clampVolume(40)); + assertEquals(100, UserInputGuard.clampVolume(101)); + assertEquals(0, UserInputGuard.sanitizeUiFlags(-1)); + assertEquals(0xFFFF, UserInputGuard.sanitizeUiFlags(0x1FFFF)); + } +}