diff --git a/Emulator/src/main/java/com/eu/habbo/messages/rcon/GiveBadge.java b/Emulator/src/main/java/com/eu/habbo/messages/rcon/GiveBadge.java index 4cb93feb..f233b32c 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/rcon/GiveBadge.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/rcon/GiveBadge.java @@ -5,6 +5,10 @@ import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.HabboBadge; import com.eu.habbo.messages.outgoing.users.AddUserBadgeComposer; import com.google.gson.Gson; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Size; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -89,9 +93,13 @@ public class GiveBadge extends RCONMessage { static class GiveBadgeJSON { + @Positive(message = "invalid user") public int user_id = -1; + @NotBlank(message = "invalid badge") + @Size(max = 512, message = "invalid badge") + @Pattern(regexp = "[A-Za-z0-9_\\-;]+", message = "invalid badge") public String badge; } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/rcon/GiveCredits.java b/Emulator/src/main/java/com/eu/habbo/messages/rcon/GiveCredits.java index 60e9326a..a5b8fb07 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/rcon/GiveCredits.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/rcon/GiveCredits.java @@ -3,6 +3,7 @@ package com.eu.habbo.messages.rcon; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.users.Habbo; import com.google.gson.Gson; +import jakarta.validation.constraints.Positive; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,9 +41,11 @@ public class GiveCredits extends RCONMessage { static class JSONGiveCredits { + @Positive(message = "invalid user") public int user_id; + @Positive(message = "invalid credits") public int credits; } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/rcon/GivePixels.java b/Emulator/src/main/java/com/eu/habbo/messages/rcon/GivePixels.java index 45001f98..cf499056 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/rcon/GivePixels.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/rcon/GivePixels.java @@ -3,6 +3,7 @@ package com.eu.habbo.messages.rcon; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.users.Habbo; import com.google.gson.Gson; +import jakarta.validation.constraints.Positive; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,9 +41,11 @@ public class GivePixels extends RCONMessage { static class JSONGivePixels { + @Positive(message = "invalid user") public int user_id; + @Positive(message = "invalid pixels") public int pixels; } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/rcon/GivePoints.java b/Emulator/src/main/java/com/eu/habbo/messages/rcon/GivePoints.java index 2afff479..1d28481d 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/rcon/GivePoints.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/rcon/GivePoints.java @@ -3,6 +3,8 @@ package com.eu.habbo.messages.rcon; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.users.Habbo; import com.google.gson.Gson; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Positive; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,12 +44,15 @@ public class GivePoints extends RCONMessage { static class JSONGivePoints { + @Positive(message = "invalid user") public int user_id; + @Positive(message = "invalid points") public int points; + @Min(value = 0, message = "invalid currency type") public int type; } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/rcon/RCONMessage.java b/Emulator/src/main/java/com/eu/habbo/messages/rcon/RCONMessage.java index 9948ee23..da13df21 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/rcon/RCONMessage.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/rcon/RCONMessage.java @@ -31,6 +31,17 @@ public abstract class RCONMessage { public abstract void handle(Gson gson, T json); + public boolean validate(T json) { + String validationError = RconPayloadValidator.validate(json); + if (validationError == null) { + return true; + } + + this.status = STATUS_ERROR; + this.message = validationError; + return false; + } + @SuppressWarnings("rawtypes") public static class RCONMessageSerializer implements JsonSerializer { @Override diff --git a/Emulator/src/main/java/com/eu/habbo/messages/rcon/RconPayloadValidator.java b/Emulator/src/main/java/com/eu/habbo/messages/rcon/RconPayloadValidator.java new file mode 100644 index 00000000..52680ae6 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/rcon/RconPayloadValidator.java @@ -0,0 +1,37 @@ +package com.eu.habbo.messages.rcon; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator; + +import java.util.Comparator; +import java.util.Set; + +final class RconPayloadValidator { + private static final Validator VALIDATOR; + + static { + ValidatorFactory factory = Validation.byDefaultProvider() + .configure() + .messageInterpolator(new ParameterMessageInterpolator()) + .buildValidatorFactory(); + VALIDATOR = factory.getValidator(); + } + + private RconPayloadValidator() { + } + + static String validate(Object payload) { + if (payload == null) { + return "invalid payload"; + } + + Set> violations = VALIDATOR.validate(payload); + return violations.stream() + .min(Comparator.comparing(violation -> violation.getPropertyPath().toString())) + .map(ConstraintViolation::getMessage) + .orElse(null); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/rcon/SetRank.java b/Emulator/src/main/java/com/eu/habbo/messages/rcon/SetRank.java index 639f9674..e05bee9e 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/rcon/SetRank.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/rcon/SetRank.java @@ -3,6 +3,7 @@ package com.eu.habbo.messages.rcon; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.users.Habbo; import com.google.gson.Gson; +import jakarta.validation.constraints.Positive; public class SetRank extends RCONMessage { @@ -31,9 +32,11 @@ public class SetRank extends RCONMessage { static class JSONSetRank { + @Positive(message = "invalid user") public int user_id; + @Positive(message = "invalid rank") public int rank; } } diff --git a/Emulator/src/main/java/com/eu/habbo/networking/rconserver/RCONServer.java b/Emulator/src/main/java/com/eu/habbo/networking/rconserver/RCONServer.java index 95d14d99..298db96c 100644 --- a/Emulator/src/main/java/com/eu/habbo/networking/rconserver/RCONServer.java +++ b/Emulator/src/main/java/com/eu/habbo/networking/rconserver/RCONServer.java @@ -119,7 +119,10 @@ public class RCONServer extends Server { try { RCONMessage rcon = message.getDeclaredConstructor().newInstance(); Gson gson = this.gsonBuilder.create(); - rcon.handle(gson, gson.fromJson(body, rcon.type)); + Object payload = gson.fromJson(body, rcon.type); + if (rcon.validate(payload)) { + rcon.handle(gson, payload); + } LOGGER.info("Handled RCON Message: {}", message.getSimpleName()); result = gson.toJson(rcon, RCONMessage.class); diff --git a/Emulator/src/test/java/com/eu/habbo/messages/rcon/RconPayloadValidatorTest.java b/Emulator/src/test/java/com/eu/habbo/messages/rcon/RconPayloadValidatorTest.java new file mode 100644 index 00000000..a95806e6 --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/messages/rcon/RconPayloadValidatorTest.java @@ -0,0 +1,44 @@ +package com.eu.habbo.messages.rcon; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class RconPayloadValidatorTest { + @Test + void acceptsValidAnnotatedPayloads() { + SetRank.JSONSetRank payload = new SetRank.JSONSetRank(); + payload.user_id = 1; + payload.rank = 2; + + assertNull(RconPayloadValidator.validate(payload)); + } + + @Test + void rejectsInvalidSetRankPayloadsBeforeDispatch() { + SetRank.JSONSetRank payload = new SetRank.JSONSetRank(); + payload.user_id = 0; + payload.rank = 2; + + assertEquals("invalid user", RconPayloadValidator.validate(payload)); + } + + @Test + void rejectsInvalidGrantPayloadsBeforeDispatch() { + GiveCredits.JSONGiveCredits payload = new GiveCredits.JSONGiveCredits(); + payload.user_id = 1; + payload.credits = 0; + + assertEquals("invalid credits", RconPayloadValidator.validate(payload)); + } + + @Test + void rejectsBlankBadgePayloadsBeforeDispatch() { + GiveBadge.GiveBadgeJSON payload = new GiveBadge.GiveBadgeJSON(); + payload.user_id = 1; + payload.badge = " "; + + assertEquals("invalid badge", RconPayloadValidator.validate(payload)); + } +}