From aa6dcd1062ffd8ac8d497ea1b243c2136419cd3d Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sun, 14 Jun 2026 21:40:59 +0200 Subject: [PATCH] fix(rcon): bound alert payloads --- .../eu/habbo/messages/rcon/HotelAlert.java | 7 +++ .../habbo/messages/rcon/ImageAlertUser.java | 18 ++++++- .../habbo/messages/rcon/ImageHotelAlert.java | 16 +++++- .../messages/rcon/AlertPayloadGuardTest.java | 50 +++++++++++++++++++ 4 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 Emulator/src/test/java/com/eu/habbo/messages/rcon/AlertPayloadGuardTest.java diff --git a/Emulator/src/main/java/com/eu/habbo/messages/rcon/HotelAlert.java b/Emulator/src/main/java/com/eu/habbo/messages/rcon/HotelAlert.java index 70989239..3713d9e0 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/rcon/HotelAlert.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/rcon/HotelAlert.java @@ -6,6 +6,9 @@ import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.outgoing.generic.alerts.GenericAlertComposer; import com.eu.habbo.messages.outgoing.generic.alerts.StaffAlertWithLinkComposer; import com.google.gson.Gson; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; import java.util.Map; @@ -37,9 +40,13 @@ public class HotelAlert extends RCONMessage { static class JSONHotelAlert { + @NotBlank(message = "invalid message") + @Size(max = 4096, message = "invalid message") public String message; + @Size(max = 2048, message = "invalid url") + @Pattern(regexp = "^$|https?://.+", message = "invalid url") public String url = ""; } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/rcon/ImageAlertUser.java b/Emulator/src/main/java/com/eu/habbo/messages/rcon/ImageAlertUser.java index 12afed25..61fe3424 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/rcon/ImageAlertUser.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/rcon/ImageAlertUser.java @@ -5,6 +5,10 @@ import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer; import com.google.gson.Gson; import gnu.trove.map.hash.THashMap; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Size; public class ImageAlertUser extends RCONMessage { public ImageAlertUser() { @@ -51,27 +55,39 @@ public class ImageAlertUser extends RCONMessage { static class JSON { + @Positive(message = "invalid user") public int user_id; + @NotBlank(message = "invalid bubble") + @Size(max = 64, message = "invalid bubble") + @Pattern(regexp = "[A-Za-z0-9_.-]+", message = "invalid bubble") public String bubble_key = ""; + @Size(max = 4096, message = "invalid message") public String message = ""; + @Size(max = 2048, message = "invalid url") + @Pattern(regexp = "^$|https?://.+", message = "invalid url") public String url = ""; + @Size(max = 256, message = "invalid url title") public String url_message = ""; + @Size(max = 256, message = "invalid title") public String title = ""; + @Size(max = 32, message = "invalid display") + @Pattern(regexp = "^$|[A-Za-z0-9_.-]+", message = "invalid display") public String display_type = ""; + @Size(max = 2048, message = "invalid image") public String image = ""; } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/rcon/ImageHotelAlert.java b/Emulator/src/main/java/com/eu/habbo/messages/rcon/ImageHotelAlert.java index d392a6b2..742b8d19 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/rcon/ImageHotelAlert.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/rcon/ImageHotelAlert.java @@ -6,6 +6,9 @@ import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer; import com.google.gson.Gson; import gnu.trove.map.hash.THashMap; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; import java.util.Map; @@ -55,24 +58,35 @@ public class ImageHotelAlert extends RCONMessage { static class JSON { + @NotBlank(message = "invalid bubble") + @Size(max = 64, message = "invalid bubble") + @Pattern(regexp = "[A-Za-z0-9_.-]+", message = "invalid bubble") public String bubble_key = ""; + @Size(max = 4096, message = "invalid message") public String message = ""; + @Size(max = 2048, message = "invalid url") + @Pattern(regexp = "^$|https?://.+", message = "invalid url") public String url = ""; + @Size(max = 256, message = "invalid url title") public String url_message = ""; + @Size(max = 256, message = "invalid title") public String title = ""; + @Size(max = 32, message = "invalid display") + @Pattern(regexp = "^$|[A-Za-z0-9_.-]+", message = "invalid display") public String display_type = ""; + @Size(max = 2048, message = "invalid image") public String image = ""; } -} \ No newline at end of file +} diff --git a/Emulator/src/test/java/com/eu/habbo/messages/rcon/AlertPayloadGuardTest.java b/Emulator/src/test/java/com/eu/habbo/messages/rcon/AlertPayloadGuardTest.java new file mode 100644 index 00000000..c885bff6 --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/messages/rcon/AlertPayloadGuardTest.java @@ -0,0 +1,50 @@ +package com.eu.habbo.messages.rcon; + +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 AlertPayloadGuardTest { + @Test + void hotelAlertPayloadIsBoundedAndUrlValidated() throws Exception { + String source = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/rcon/HotelAlert.java")); + + assertTrue(source.contains("@NotBlank(message = \"invalid message\")"), + "HotelAlert must reject blank global alerts"); + assertTrue(source.contains("@Size(max = 4096"), + "HotelAlert must bound global alert text"); + assertTrue(source.contains("@Pattern(regexp = \"^$|https?://.+\""), + "HotelAlert must reject non-http alert links"); + } + + @Test + void imageHotelAlertPayloadIsBounded() throws Exception { + String source = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/rcon/ImageHotelAlert.java")); + + assertTrue(source.contains("@NotBlank(message = \"invalid bubble\")"), + "ImageHotelAlert must require a bubble key"); + assertTrue(source.contains("@Pattern(regexp = \"[A-Za-z0-9_.-]+\""), + "ImageHotelAlert bubble keys must be constrained to safe token characters"); + assertTrue(source.contains("@Size(max = 2048"), + "ImageHotelAlert URL/image fields must be bounded"); + assertTrue(source.contains("@Pattern(regexp = \"^$|https?://.+\""), + "ImageHotelAlert must reject non-http links"); + } + + @Test + void imageUserAlertPayloadIsBoundedAndTargetsValidUsers() throws Exception { + String source = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/rcon/ImageAlertUser.java")); + + assertTrue(source.contains("@Positive(message = \"invalid user\")"), + "ImageAlertUser must reject invalid target users before execution"); + assertTrue(source.contains("@NotBlank(message = \"invalid bubble\")"), + "ImageAlertUser must require a bubble key"); + assertTrue(source.contains("@Size(max = 4096"), + "ImageAlertUser must bound alert text"); + assertTrue(source.contains("@Pattern(regexp = \"^$|https?://.+\""), + "ImageAlertUser must reject non-http links"); + } +}