You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-19 15:06:19 +00:00
Merge pull request #217 from simoleo89/fix/nitro-secure-asset-safety
fix(auth): bound secure asset file reads
This commit is contained in:
+13
@@ -35,6 +35,8 @@ public class NitroSecureAssetHandler extends ChannelInboundHandlerAdapter {
|
||||
private static final String BOOTSTRAP_PATH = "/nitro-sec/bootstrap";
|
||||
private static final String FILE_PATH = "/nitro-sec/file";
|
||||
private static final int MAX_BOOTSTRAP_BODY_BYTES = 4096;
|
||||
private static final int DEFAULT_MAX_CONFIG_BYTES = 2 * 1024 * 1024;
|
||||
private static final int DEFAULT_MAX_GAMEDATA_BYTES = 16 * 1024 * 1024;
|
||||
private static final SecureRandom RNG = new SecureRandom();
|
||||
private static final KeyPair SERVER_KEYPAIR = createServerKeyPair();
|
||||
private static final String SERVER_KEY_FINGERPRINT = fingerprint(SERVER_KEYPAIR.getPublic().getEncoded());
|
||||
@@ -146,6 +148,9 @@ public class NitroSecureAssetHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
if (!target.startsWith(root)) throw new IllegalArgumentException("Invalid file.");
|
||||
if (!Files.isRegularFile(target)) throw new IOException("Not found");
|
||||
long size = Files.size(target);
|
||||
int maxBytes = maxAssetBytes(kind);
|
||||
if (size > maxBytes) throw new IllegalArgumentException("File too large.");
|
||||
|
||||
String cacheKey = kind + ":" + target;
|
||||
long modified = Files.getLastModifiedTime(target).toMillis();
|
||||
@@ -158,6 +163,14 @@ public class NitroSecureAssetHandler extends ChannelInboundHandlerAdapter {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static int maxAssetBytes(String kind) {
|
||||
boolean config = "config".equals(kind);
|
||||
String key = config ? "nitro.secure.config.max_file_bytes" : "nitro.secure.gamedata.max_file_bytes";
|
||||
int fallback = config ? DEFAULT_MAX_CONFIG_BYTES : DEFAULT_MAX_GAMEDATA_BYTES;
|
||||
int configured = Emulator.getConfig().getInt(key, fallback);
|
||||
return configured > 0 ? configured : fallback;
|
||||
}
|
||||
|
||||
private static String normalizeFile(String file) {
|
||||
if (file == null) throw new IllegalArgumentException("Missing file.");
|
||||
String value = URLDecoder.decode(file, StandardCharsets.UTF_8).replace('\\', '/');
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package com.eu.habbo.networking.gameserver.auth;
|
||||
|
||||
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 NitroSecureAssetHandlerContractTest {
|
||||
private static String handlerSource() throws Exception {
|
||||
return Files.readString(Path.of("src/main/java/com/eu/habbo/networking/gameserver/auth/NitroSecureAssetHandler.java"));
|
||||
}
|
||||
|
||||
private static String emulatorSource() throws Exception {
|
||||
return Files.readString(Path.of("src/main/java/com/eu/habbo/Emulator.java"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void secureAssetFilesAreSizeCheckedBeforeReadAndCache() throws Exception {
|
||||
String handler = handlerSource();
|
||||
String emulator = emulatorSource();
|
||||
|
||||
int size = handler.indexOf("long size = Files.size(target)");
|
||||
int maxBytes = handler.indexOf("int maxBytes = maxAssetBytes(kind)", size);
|
||||
int oversizedGuard = handler.indexOf("size > maxBytes", maxBytes);
|
||||
int cacheLookup = handler.indexOf("CACHE.get(cacheKey)", oversizedGuard);
|
||||
int readAllBytes = handler.indexOf("Files.readAllBytes(target)", oversizedGuard);
|
||||
|
||||
assertTrue(handler.contains("DEFAULT_MAX_CONFIG_BYTES = 2 * 1024 * 1024"),
|
||||
"Secure config assets should have a conservative default file cap");
|
||||
assertTrue(handler.contains("DEFAULT_MAX_GAMEDATA_BYTES = 16 * 1024 * 1024"),
|
||||
"Secure gamedata assets should have a bounded default file cap");
|
||||
assertTrue(handler.contains("nitro.secure.config.max_file_bytes"),
|
||||
"Secure config max file size should be configurable");
|
||||
assertTrue(handler.contains("nitro.secure.gamedata.max_file_bytes"),
|
||||
"Secure gamedata max file size should be configurable");
|
||||
assertTrue(size > -1, "Secure assets must inspect file size before loading bytes");
|
||||
assertTrue(maxBytes > size, "Secure assets must resolve the configured cap before loading bytes");
|
||||
assertTrue(oversizedGuard > maxBytes, "Secure assets must reject oversized files");
|
||||
assertTrue(oversizedGuard < cacheLookup, "Oversized secure assets must not be served from cache");
|
||||
assertTrue(oversizedGuard < readAllBytes, "Oversized secure assets must be rejected before readAllBytes");
|
||||
assertTrue(emulator.contains("register(\"nitro.secure.config.max_file_bytes\", \"2097152\")"),
|
||||
"Secure config max file size default must be registered before startup");
|
||||
assertTrue(emulator.contains("register(\"nitro.secure.gamedata.max_file_bytes\", \"16777216\")"),
|
||||
"Secure gamedata max file size default must be registered before startup");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user