From 827b130ccc77933ecd5c33d6e652e04c3fab31c5 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 15 Jun 2026 22:15:39 +0200 Subject: [PATCH] fix(rooms): guard room user moderation packets --- .../rooms/users/RoomUserMuteEvent.java | 9 ++++ .../rooms/users/RoomUserSignEvent.java | 16 +++++--- .../users/RoomUserMuteGuardContractTest.java | 39 ++++++++++++++++++ .../users/RoomUserSignGuardContractTest.java | 41 +++++++++++++++++++ 4 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 Emulator/src/test/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserMuteGuardContractTest.java create mode 100644 Emulator/src/test/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserSignGuardContractTest.java diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserMuteEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserMuteEvent.java index 1e91d572..7a8a55bf 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserMuteEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserMuteEvent.java @@ -9,6 +9,9 @@ import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.users.MutedWhisperComposer; public class RoomUserMuteEvent extends MessageHandler { + private static final int MIN_MUTE_MINUTES = 1; + private static final int MAX_MUTE_MINUTES = 1440; + @Override public void handle() throws Exception { int userId = this.packet.readInt(); @@ -24,6 +27,12 @@ public class RoomUserMuteEvent extends MessageHandler { Habbo habbo = room.getHabbo(userId); if (habbo != null) { + if (minutes < MIN_MUTE_MINUTES || minutes > MAX_MUTE_MINUTES) + return; + + if (habbo.hasPermission(Permission.ACC_UNKICKABLE)) + return; + room.muteHabbo(habbo, minutes); habbo.getClient().sendResponse(new MutedWhisperComposer(minutes * 60)); AchievementManager.progressAchievement(this.client.getHabbo(), Emulator.getGameEnvironment().getAchievementManager().getAchievement("SelfModMuteSeen")); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserSignEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserSignEvent.java index 0a8a2d7d..52133a0a 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserSignEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserSignEvent.java @@ -11,10 +11,16 @@ import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.plugin.events.users.UserSignEvent; public class RoomUserSignEvent extends MessageHandler { + private static final int MIN_SIGN_ID = 0; + private static final int MAX_SIGN_ID = 10; + @Override public void handle() throws Exception { int signId = this.packet.readInt(); + if (signId < MIN_SIGN_ID || signId > MAX_SIGN_ID) + return; + Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); if (room == null) @@ -26,12 +32,10 @@ public class RoomUserSignEvent extends MessageHandler { this.client.getHabbo().getHabboInfo().getCurrentRoom().unIdle(this.client.getHabbo()); WiredManager.triggerUserPerformsAction(room, this.client.getHabbo().getRoomUnit(), WiredUserActionType.SIGN, event.sign); - if(signId <= 10) { - int userId = this.client.getHabbo().getHabboInfo().getId(); - for (HabboItem item : room.getRoomSpecialTypes().getItemsOfType(InteractionVoteCounter.class)) { - if (item instanceof InteractionVoteCounter) { - ((InteractionVoteCounter)item).vote(room, userId, signId); - } + int userId = this.client.getHabbo().getHabboInfo().getId(); + for (HabboItem item : room.getRoomSpecialTypes().getItemsOfType(InteractionVoteCounter.class)) { + if (item instanceof InteractionVoteCounter) { + ((InteractionVoteCounter)item).vote(room, userId, signId); } } } diff --git a/Emulator/src/test/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserMuteGuardContractTest.java b/Emulator/src/test/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserMuteGuardContractTest.java new file mode 100644 index 00000000..fa5f2224 --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserMuteGuardContractTest.java @@ -0,0 +1,39 @@ +package com.eu.habbo.messages.incoming.rooms.users; + +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class RoomUserMuteGuardContractTest { + + private static String source() throws Exception { + return Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserMuteEvent.java")); + } + + @Test + void muteDurationIsBoundedBeforeApplyingMute() throws Exception { + String source = source(); + + int targetLookup = source.indexOf("Habbo habbo = room.getHabbo(userId)"); + int durationGuard = source.indexOf("minutes < MIN_MUTE_MINUTES || minutes > MAX_MUTE_MINUTES", targetLookup); + int muteCall = source.indexOf("room.muteHabbo(habbo, minutes)", targetLookup); + + assertTrue(targetLookup > -1, "Mute handler must resolve the room target"); + assertTrue(durationGuard > targetLookup, "Mute handler must bound client-provided minutes"); + assertTrue(durationGuard < muteCall, "Mute duration must be validated before mutating room state"); + } + + @Test + void unkickableTargetsCannotBeMutedThroughRoomPacket() throws Exception { + String source = source(); + + int unkickableGuard = source.indexOf("habbo.hasPermission(Permission.ACC_UNKICKABLE)"); + int muteCall = source.indexOf("room.muteHabbo(habbo, minutes)"); + + assertTrue(unkickableGuard > -1, "Room mute must respect ACC_UNKICKABLE like kick and ban"); + assertTrue(unkickableGuard < muteCall, "Unkickable targets must be rejected before muting"); + } +} diff --git a/Emulator/src/test/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserSignGuardContractTest.java b/Emulator/src/test/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserSignGuardContractTest.java new file mode 100644 index 00000000..ed2f01bb --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserSignGuardContractTest.java @@ -0,0 +1,41 @@ +package com.eu.habbo.messages.incoming.rooms.users; + +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class RoomUserSignGuardContractTest { + + private static String source() throws Exception { + return Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserSignEvent.java")); + } + + @Test + void signIdIsValidatedBeforeRoomStateAndWiredTriggers() throws Exception { + String source = source(); + + int signRead = source.indexOf("int signId = this.packet.readInt()"); + int guard = source.indexOf("signId < MIN_SIGN_ID || signId > MAX_SIGN_ID", signRead); + int status = source.indexOf("setStatus(RoomUnitStatus.SIGN", signRead); + int wired = source.indexOf("WiredManager.triggerUserPerformsAction", signRead); + + assertTrue(signRead > -1, "Sign handler must read the client-provided sign id"); + assertTrue(guard > signRead, "Sign handler must reject out-of-range sign ids"); + assertTrue(guard < status, "Sign id must be validated before status mutation"); + assertTrue(guard < wired, "Sign id must be validated before wired triggers"); + } + + @Test + void voteCountersOnlyReceiveValidatedSigns() throws Exception { + String source = source(); + + int guard = source.indexOf("signId < MIN_SIGN_ID || signId > MAX_SIGN_ID"); + int vote = source.indexOf(".vote(room, userId, signId)"); + + assertTrue(guard > -1, "Sign id range guard must exist"); + assertTrue(vote > guard, "Vote counters must only receive signs after the range guard"); + } +}