From edfd52af23588922e5172d1c07c58dd62ed3f52a Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 8 Aug 2019 13:00:22 -0400 Subject: [PATCH] config service: validate config values --- .../http/service/config/ConfigService.java | 79 +++++++++++++++++++ .../service/config/ConfigServiceTest.java | 12 +++ 2 files changed, 91 insertions(+) diff --git a/http-service/src/main/java/net/runelite/http/service/config/ConfigService.java b/http-service/src/main/java/net/runelite/http/service/config/ConfigService.java index 395c46f64d..bbe8557d88 100644 --- a/http-service/src/main/java/net/runelite/http/service/config/ConfigService.java +++ b/http-service/src/main/java/net/runelite/http/service/config/ConfigService.java @@ -26,6 +26,10 @@ package net.runelite.http.service.config; import com.google.common.annotations.VisibleForTesting; import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; import com.google.gson.JsonSyntaxException; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoCollection; @@ -52,6 +56,9 @@ import org.springframework.stereotype.Service; @Service public class ConfigService { + private static final int MAX_DEPTH = 8; + private static final int MAX_VALUE_LENGTH = 262144; + private final Gson GSON = RuneLiteAPI.GSON; private final UpdateOptions upsertUpdateOptions = new UpdateOptions().upsert(true); @@ -139,6 +146,11 @@ public class ConfigService return false; } + if (!validateJson(value)) + { + return false; + } + Object jsonValue = parseJsonString(value); mongoCollection.updateOne(eq("_userId", userId), set(split[0] + "." + split[1].replace('.', ':'), jsonValue), @@ -205,4 +217,71 @@ public class ConfigService } return jsonValue; } + + @VisibleForTesting + static boolean validateJson(String value) + { + try + { + // I couldn't figure out a better way to do this than a second json parse + JsonElement jsonElement = RuneLiteAPI.GSON.fromJson(value, JsonElement.class); + return validateObject(jsonElement, 1); + } + catch (JsonSyntaxException ex) + { + // the client submits the string representation of objects which is not always valid json, + // eg. a value with a ':' in it. We just ignore it now. We can't json encode the values client + // side due to them already being strings, which prevents gson from being able to convert them + // to ints/floats/maps etc. + return value.length() < MAX_VALUE_LENGTH; + } + } + + private static boolean validateObject(JsonElement jsonElement, int depth) + { + if (depth >= MAX_DEPTH) + { + return false; + } + + if (jsonElement.isJsonObject()) + { + JsonObject jsonObject = jsonElement.getAsJsonObject(); + + for (Map.Entry entry : jsonObject.entrySet()) + { + JsonElement element = entry.getValue(); + + if (!validateObject(element, depth + 1)) + { + return false; + } + } + } + else if (jsonElement.isJsonArray()) + { + JsonArray jsonArray = jsonElement.getAsJsonArray(); + + for (int i = 0; i < jsonArray.size(); ++i) + { + JsonElement element = jsonArray.get(i); + + if (!validateObject(element, depth + 1)) + { + return false; + } + } + } + else if (jsonElement.isJsonPrimitive()) + { + JsonPrimitive jsonPrimitive = jsonElement.getAsJsonPrimitive(); + String value = jsonPrimitive.getAsString(); + if (value.length() >= MAX_VALUE_LENGTH) + { + return false; + } + } + + return true; + } } diff --git a/http-service/src/test/java/net/runelite/http/service/config/ConfigServiceTest.java b/http-service/src/test/java/net/runelite/http/service/config/ConfigServiceTest.java index 09e94c5d56..64c4ef574c 100644 --- a/http-service/src/test/java/net/runelite/http/service/config/ConfigServiceTest.java +++ b/http-service/src/test/java/net/runelite/http/service/config/ConfigServiceTest.java @@ -26,6 +26,7 @@ package net.runelite.http.service.config; import com.google.common.collect.ImmutableMap; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import org.junit.Test; public class ConfigServiceTest @@ -40,4 +41,15 @@ public class ConfigServiceTest assertEquals("test", ConfigService.parseJsonString("\"test\"")); assertEquals(ImmutableMap.of("key", "value"), ConfigService.parseJsonString("{\"key\": \"value\"}")); } + + @Test + public void testValidateJson() + { + assertTrue(ConfigService.validateJson("1")); + assertTrue(ConfigService.validateJson("3.14")); + assertTrue(ConfigService.validateJson("test")); + assertTrue(ConfigService.validateJson("\"test\"")); + assertTrue(ConfigService.validateJson("key:value")); + assertTrue(ConfigService.validateJson("{\"key\": \"value\"}")); + } } \ No newline at end of file