fix(rcon): validate privileged payloads

This commit is contained in:
simoleo89
2026-06-14 18:42:15 +02:00
parent 994d539caf
commit d7fa02a453
9 changed files with 118 additions and 1 deletions
@@ -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<GiveBadge.GiveBadgeJSON> {
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;
}
}
@@ -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<GiveCredits.JSONGiveCredits> {
static class JSONGiveCredits {
@Positive(message = "invalid user")
public int user_id;
@Positive(message = "invalid credits")
public int credits;
}
}
@@ -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<GivePixels.JSONGivePixels> {
static class JSONGivePixels {
@Positive(message = "invalid user")
public int user_id;
@Positive(message = "invalid pixels")
public int pixels;
}
}
@@ -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<GivePoints.JSONGivePoints> {
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;
}
}
@@ -31,6 +31,17 @@ public abstract class RCONMessage<T> {
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<RCONMessage> {
@Override
@@ -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<ConstraintViolation<Object>> violations = VALIDATOR.validate(payload);
return violations.stream()
.min(Comparator.comparing(violation -> violation.getPropertyPath().toString()))
.map(ConstraintViolation::getMessage)
.orElse(null);
}
}
@@ -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<SetRank.JSONSetRank> {
@@ -31,9 +32,11 @@ public class SetRank extends RCONMessage<SetRank.JSONSetRank> {
static class JSONSetRank {
@Positive(message = "invalid user")
public int user_id;
@Positive(message = "invalid rank")
public int rank;
}
}
@@ -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);
@@ -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));
}
}