From 044d1141cdbb86c4d9da1091298601ee29863fd6 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 15 Jun 2026 20:01:34 +0200 Subject: [PATCH] fix(modtool): validate report payloads --- .../modtool/ModToolReportInputGuard.java | 30 ++++++++ .../incoming/modtool/ReportBullyEvent.java | 6 +- .../incoming/modtool/ReportCommentEvent.java | 10 ++- .../incoming/modtool/ReportEvent.java | 20 ++++-- .../modtool/ReportFriendPrivateChatEvent.java | 20 +++++- .../incoming/modtool/ReportPhotoEvent.java | 6 ++ .../incoming/modtool/ReportThreadEvent.java | 9 ++- .../ModToolReportInputContractTest.java | 69 +++++++++++++++++++ .../modtool/ModToolReportInputGuardTest.java | 37 ++++++++++ 9 files changed, 196 insertions(+), 11 deletions(-) create mode 100644 Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolReportInputGuard.java create mode 100644 Emulator/src/test/java/com/eu/habbo/messages/incoming/modtool/ModToolReportInputContractTest.java create mode 100644 Emulator/src/test/java/com/eu/habbo/messages/incoming/modtool/ModToolReportInputGuardTest.java diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolReportInputGuard.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolReportInputGuard.java new file mode 100644 index 00000000..f265096d --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolReportInputGuard.java @@ -0,0 +1,30 @@ +package com.eu.habbo.messages.incoming.modtool; + +final class ModToolReportInputGuard { + static final int MAX_REPORT_MESSAGE_LENGTH = 1000; + static final int MAX_PRIVATE_CHAT_LOGS = 100; + static final int MAX_PRIVATE_CHAT_MESSAGE_LENGTH = 500; + + private ModToolReportInputGuard() { + } + + static String normalize(String value) { + return value == null ? "" : value.trim(); + } + + static boolean isValidReportMessage(String value) { + return value != null && !value.isEmpty() && value.length() <= MAX_REPORT_MESSAGE_LENGTH; + } + + static boolean isValidChatLogMessage(String value) { + return value != null && value.length() <= MAX_PRIVATE_CHAT_MESSAGE_LENGTH; + } + + static boolean isValidPrivateChatLogCount(int count) { + return count > 0 && count <= MAX_PRIVATE_CHAT_LOGS; + } + + static boolean isPositiveId(int id) { + return id > 0; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportBullyEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportBullyEvent.java index f2207f2c..7943cbc2 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportBullyEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportBullyEvent.java @@ -14,7 +14,7 @@ import java.util.ArrayList; public class ReportBullyEvent extends MessageHandler { @Override public void handle() throws Exception { - if (this.client.getHabbo().getHabboStats().allowTalk()) { + if (!this.client.getHabbo().getHabboStats().allowTalk()) { this.client.sendResponse(new HelperRequestDisabledComposer()); return; } @@ -22,7 +22,9 @@ public class ReportBullyEvent extends MessageHandler { int userId = this.packet.readInt(); int roomId = this.packet.readInt(); - if (userId == this.client.getHabbo().getHabboInfo().getId()) { + if (!ModToolReportInputGuard.isPositiveId(userId) || + !ModToolReportInputGuard.isPositiveId(roomId) || + userId == this.client.getHabbo().getHabboInfo().getId()) { return; } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportCommentEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportCommentEvent.java index b21849ba..85673001 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportCommentEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportCommentEvent.java @@ -17,7 +17,15 @@ public class ReportCommentEvent extends MessageHandler { int threadId = this.packet.readInt(); int commentId = this.packet.readInt(); int topicId = this.packet.readInt(); - String message = this.packet.readString(); + String message = ModToolReportInputGuard.normalize(this.packet.readString()); + + if (!ModToolReportInputGuard.isPositiveId(groupId) || + !ModToolReportInputGuard.isPositiveId(threadId) || + !ModToolReportInputGuard.isPositiveId(commentId) || + !ModToolReportInputGuard.isPositiveId(topicId) || + !ModToolReportInputGuard.isValidReportMessage(message)) { + return; + } CfhTopic topic = Emulator.getGameEnvironment().getModToolManager().getCfhTopic(topicId); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportEvent.java index 99c8ba6c..aabb2dbc 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportEvent.java @@ -21,12 +21,26 @@ public class ReportEvent extends MessageHandler { return; } - String message = this.packet.readString(); + String message = ModToolReportInputGuard.normalize(this.packet.readString()); int topic = this.packet.readInt(); int userId = this.packet.readInt(); int roomId = this.packet.readInt(); this.packet.readInt(); + if (!ModToolReportInputGuard.isValidReportMessage(message) || + topic <= 0 || + (userId != -1 && !ModToolReportInputGuard.isPositiveId(userId)) || + !ModToolReportInputGuard.isPositiveId(roomId) || + userId == this.client.getHabbo().getHabboInfo().getId()) { + return; + } + + CfhTopic cfhTopic = Emulator.getGameEnvironment().getModToolManager().getCfhTopic(topic); + + if (cfhTopic == null) { + return; + } + Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(roomId); List issues = Emulator.getGameEnvironment().getModToolManager().openTicketsForHabbo(this.client.getHabbo()); if (!issues.isEmpty()) { @@ -35,8 +49,6 @@ public class ReportEvent extends MessageHandler { return; } - CfhTopic cfhTopic = Emulator.getGameEnvironment().getModToolManager().getCfhTopic(topic); - if (userId != -1) { Habbo reported = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId); @@ -117,4 +129,4 @@ public class ReportEvent extends MessageHandler { } } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportFriendPrivateChatEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportFriendPrivateChatEvent.java index 63e1ba43..4840679d 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportFriendPrivateChatEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportFriendPrivateChatEvent.java @@ -21,12 +21,20 @@ public class ReportFriendPrivateChatEvent extends MessageHandler { return; } - String message = this.packet.readString(); + String message = ModToolReportInputGuard.normalize(this.packet.readString()); int category = this.packet.readInt(); int userId = this.packet.readInt(); int count = this.packet.readInt(); ArrayList chatLogs = new ArrayList<>(); + if (!ModToolReportInputGuard.isValidReportMessage(message) || + category <= 0 || + !ModToolReportInputGuard.isPositiveId(userId) || + userId == this.client.getHabbo().getHabboInfo().getId() || + !ModToolReportInputGuard.isValidPrivateChatLogCount(count)) { + return; + } + HabboInfo info; Habbo target = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId); if (target != null) { @@ -37,11 +45,17 @@ public class ReportFriendPrivateChatEvent extends MessageHandler { if (info == null) return; - for (int i = 0; i < Math.min(count, 100); i++) { + for (int i = 0; i < count; i++) { int chatUserId = this.packet.readInt(); String username = this.packet.readInt() == info.getId() ? info.getUsername() : this.client.getHabbo().getHabboInfo().getUsername(); + String chatMessage = ModToolReportInputGuard.normalize(this.packet.readString()); - chatLogs.add(new ModToolChatLog(0, chatUserId, username, this.packet.readString())); + if (!ModToolReportInputGuard.isPositiveId(chatUserId) || + !ModToolReportInputGuard.isValidChatLogMessage(chatMessage)) { + return; + } + + chatLogs.add(new ModToolChatLog(0, chatUserId, username, chatMessage)); } ModToolIssue issue = new ModToolIssue(this.client.getHabbo().getHabboInfo().getId(), this.client.getHabbo().getHabboInfo().getUsername(), userId, info.getUsername(), 0, message, ModToolTicketType.IM); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportPhotoEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportPhotoEvent.java index fc225fb5..fc6e1f04 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportPhotoEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportPhotoEvent.java @@ -28,6 +28,12 @@ public class ReportPhotoEvent extends MessageHandler { int topicId = this.packet.readInt(); int itemId = this.packet.readInt(); + if (!ModToolReportInputGuard.isPositiveId(roomId) || + !ModToolReportInputGuard.isPositiveId(topicId) || + !ModToolReportInputGuard.isPositiveId(itemId)) { + return; + } + CfhTopic topic = Emulator.getGameEnvironment().getModToolManager().getCfhTopic(topicId); if (topic == null) return; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportThreadEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportThreadEvent.java index 50bcab89..b1e0b8b1 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportThreadEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ReportThreadEvent.java @@ -16,7 +16,14 @@ public class ReportThreadEvent extends MessageHandler { int groupId = this.packet.readInt(); int threadId = this.packet.readInt(); int topicId = this.packet.readInt(); - String message = this.packet.readString(); + String message = ModToolReportInputGuard.normalize(this.packet.readString()); + + if (!ModToolReportInputGuard.isPositiveId(groupId) || + !ModToolReportInputGuard.isPositiveId(threadId) || + !ModToolReportInputGuard.isPositiveId(topicId) || + !ModToolReportInputGuard.isValidReportMessage(message)) { + return; + } CfhTopic topic = Emulator.getGameEnvironment().getModToolManager().getCfhTopic(topicId); diff --git a/Emulator/src/test/java/com/eu/habbo/messages/incoming/modtool/ModToolReportInputContractTest.java b/Emulator/src/test/java/com/eu/habbo/messages/incoming/modtool/ModToolReportInputContractTest.java new file mode 100644 index 00000000..75f26644 --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/messages/incoming/modtool/ModToolReportInputContractTest.java @@ -0,0 +1,69 @@ +package com.eu.habbo.messages.incoming.modtool; + +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ModToolReportInputContractTest { + @Test + void reportHandlersNormalizeAndBoundFreeText() throws Exception { + Path base = Path.of("src/main/java/com/eu/habbo/messages/incoming/modtool"); + + for (String handler : List.of( + "ReportEvent.java", + "ReportFriendPrivateChatEvent.java", + "ReportCommentEvent.java", + "ReportThreadEvent.java" + )) { + String source = Files.readString(base.resolve(handler)); + + assertTrue(source.contains("ModToolReportInputGuard.normalize"), + handler + " must normalize report text before persistence or staff broadcast"); + assertTrue(source.contains("ModToolReportInputGuard.isValidReportMessage"), + handler + " must reject empty or oversized report text"); + } + } + + @Test + void reportHandlersRejectInvalidIdsAndCounts() throws Exception { + Path base = Path.of("src/main/java/com/eu/habbo/messages/incoming/modtool"); + + for (String handler : List.of( + "ReportEvent.java", + "ReportFriendPrivateChatEvent.java", + "ReportCommentEvent.java", + "ReportThreadEvent.java", + "ReportBullyEvent.java", + "ReportPhotoEvent.java" + )) { + String source = Files.readString(base.resolve(handler)); + + assertTrue(source.contains("ModToolReportInputGuard.isPositiveId"), + handler + " must reject zero or negative ids supplied by the client"); + } + + String privateChat = Files.readString(base.resolve("ReportFriendPrivateChatEvent.java")); + assertTrue(privateChat.contains("ModToolReportInputGuard.isValidPrivateChatLogCount(count)"), + "private chat reports must reject negative or oversized client-provided chatlog counts"); + } + + @Test + void reportEventValidatesTopicBeforeUsingReply() throws Exception { + String source = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/modtool/ReportEvent.java")); + + assertTrue(source.indexOf("if (cfhTopic == null)") < source.indexOf("cfhTopic.reply"), + "ReportEvent must reject unknown topics before dereferencing the reply text"); + } + + @Test + void bullyReportUsesSameMutedUserGateAsNormalReports() throws Exception { + String source = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/modtool/ReportBullyEvent.java")); + + assertTrue(source.contains("if (!this.client.getHabbo().getHabboStats().allowTalk())"), + "bully reports must reject muted users instead of rejecting users who can talk"); + } +} diff --git a/Emulator/src/test/java/com/eu/habbo/messages/incoming/modtool/ModToolReportInputGuardTest.java b/Emulator/src/test/java/com/eu/habbo/messages/incoming/modtool/ModToolReportInputGuardTest.java new file mode 100644 index 00000000..91e49a46 --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/messages/incoming/modtool/ModToolReportInputGuardTest.java @@ -0,0 +1,37 @@ +package com.eu.habbo.messages.incoming.modtool; + +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 ModToolReportInputGuardTest { + @Test + void normalizesNullableMessages() { + assertEquals("", ModToolReportInputGuard.normalize(null)); + assertEquals("report", ModToolReportInputGuard.normalize(" report ")); + } + + @Test + void reportMessagesMustBeNonEmptyAndBounded() { + assertFalse(ModToolReportInputGuard.isValidReportMessage("")); + assertFalse(ModToolReportInputGuard.isValidReportMessage(null)); + assertTrue(ModToolReportInputGuard.isValidReportMessage("a".repeat(ModToolReportInputGuard.MAX_REPORT_MESSAGE_LENGTH))); + assertFalse(ModToolReportInputGuard.isValidReportMessage("a".repeat(ModToolReportInputGuard.MAX_REPORT_MESSAGE_LENGTH + 1))); + } + + @Test + void privateChatLogCountsAreBounded() { + assertFalse(ModToolReportInputGuard.isValidPrivateChatLogCount(0)); + assertTrue(ModToolReportInputGuard.isValidPrivateChatLogCount(ModToolReportInputGuard.MAX_PRIVATE_CHAT_LOGS)); + assertFalse(ModToolReportInputGuard.isValidPrivateChatLogCount(ModToolReportInputGuard.MAX_PRIVATE_CHAT_LOGS + 1)); + } + + @Test + void idsMustBePositive() { + assertFalse(ModToolReportInputGuard.isPositiveId(0)); + assertFalse(ModToolReportInputGuard.isPositiveId(-1)); + assertTrue(ModToolReportInputGuard.isPositiveId(1)); + } +}