diff --git a/LaunchServer/resources/launchserver/defaults/config.cfg b/LaunchServer/resources/launchserver/defaults/config.cfg index b0d14cb..60a3d51 100644 --- a/LaunchServer/resources/launchserver/defaults/config.cfg +++ b/LaunchServer/resources/launchserver/defaults/config.cfg @@ -2,6 +2,11 @@ bindAddress: "0.0.0.0"; port: 7240; +# Auth rate limit +authRateLimit: 2; +authRateLimitMilis: 5000; +authRejectString: "Вы превысили лимит авторизаций, подождите некоторое время!"; + # Auth handler authHandler: "textFile"; authHandlerConfig: { diff --git a/LaunchServer/source/LaunchServer.java b/LaunchServer/source/LaunchServer.java index a311327..7d5f801 100644 --- a/LaunchServer/source/LaunchServer.java +++ b/LaunchServer/source/LaunchServer.java @@ -52,6 +52,7 @@ import launcher.serialize.config.entry.StringConfigEntry; import launcher.serialize.signed.SignedObjectHolder; import launchserver.auth.AuthException; +import launchserver.auth.AuthLimiter; import launchserver.auth.MySQLSourceConfig; import launchserver.auth.handler.AuthHandler; import launchserver.auth.handler.CachedAuthHandler; @@ -82,6 +83,8 @@ @LauncherAPI public final Path updatesDir; @LauncherAPI public final Path profilesDir; + @LauncherAPI public final AuthLimiter limiter; + // Server config @LauncherAPI public final Config config; @LauncherAPI public final RSAPublicKey publicKey; @@ -165,6 +168,9 @@ } config.verify(); + // anti-brutforce + limiter = new AuthLimiter(this); + // Set launcher EXE binary launcherBinary = new JARLauncherBinary(this); launcherEXEBinary = config.launch4J ? new EXEL4JLauncherBinary(this) : new EXELauncherBinary(this); @@ -456,6 +462,11 @@ @LauncherAPI public final AuthProvider authProvider; @LauncherAPI public final TextureProvider textureProvider; + // AuthLimiter + @LauncherAPI public final int authRateLimit; + @LauncherAPI public final int authRateLimitMilis; + @LauncherAPI public final String authRejectString; + // Misc options @LauncherAPI public final boolean launch4J; @LauncherAPI public final boolean compress; @@ -470,6 +481,14 @@ bindAddress = block.hasEntry("bindAddress") ? block.getEntryValue("bindAddress", StringConfigEntry.class) : getAddress(); + // Limit Autorization + authRateLimit = VerifyHelper.verifyInt(block.getEntryValue("authRateLimit", IntegerConfigEntry.class), + VerifyHelper.range(0, 1000000), "Illegal authRateLimit"); + authRateLimitMilis = VerifyHelper.verifyInt(block.getEntryValue("authRateLimitMilis", IntegerConfigEntry.class), + VerifyHelper.range(10, 10000000), "Illegal authRateLimitMillis"); + authRejectString = block.hasEntry("authRejectString") ? + block.getEntryValue("authRejectString", StringConfigEntry.class) : "Вы превысили лимит авторизаций. Подождите некоторое время перед повторной попыткой"; + // Set handlers & providers authHandler = AuthHandler.newHandler(block.getEntryValue("authHandler", StringConfigEntry.class), block.getEntry("authHandlerConfig", BlockConfigEntry.class)); diff --git a/LaunchServer/source/auth/AuthLimiter.java b/LaunchServer/source/auth/AuthLimiter.java new file mode 100644 index 0000000..6af8a0a --- /dev/null +++ b/LaunchServer/source/auth/AuthLimiter.java @@ -0,0 +1,95 @@ +package launchserver.auth; + +import java.util.HashMap; + +import launcher.LauncherAPI; +import launchserver.LaunchServer; + +public class AuthLimiter +{ + static class AuthEntry + { + public int value; + public long ts; + + public AuthEntry(int i, long l) + { + value = i; + ts = l; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + + if (!(obj instanceof AuthEntry)) + { + return false; + } + + AuthEntry other = (AuthEntry) obj; + if (ts != other.ts) + { + return false; + } + + return value == other.value; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + (int) (ts ^ ts >>> 32); + result = prime * result + value; + return result; + } + + @Override + public String toString() + { + return String.format("AuthEntry {value=%s, ts=%s}", value, ts); + } + } + + @LauncherAPI + public static final long TIMEOUT = 10 * 60 * 1000; //10 минут + public final int rateLimit; + public final int rateLimitMilis; + + private HashMap map; + + public AuthLimiter(LaunchServer srv) + { + map = new HashMap<>(); + rateLimit = srv.config.authRateLimit; + rateLimitMilis = srv.config.authRateLimitMilis; + } + + public boolean isLimit(String ip) { + if (map.containsKey(ip)) { + AuthEntry rate = map.get(ip); + long currenttime = System.currentTimeMillis(); + if (rate.ts + rateLimitMilis < currenttime) rate.value = 0; + if (rate.value >= rateLimit && rateLimit > 0) { + rate.value++; + rate.ts = currenttime; + return true; + } + rate.value++; + rate.ts = currenttime; + return false; + } + map.put(ip, new AuthEntry(1, System.currentTimeMillis())); + return false; + } +} diff --git a/LaunchServer/source/response/auth/AuthResponse.java b/LaunchServer/source/response/auth/AuthResponse.java index 36c16d5..dd2098b 100644 --- a/LaunchServer/source/response/auth/AuthResponse.java +++ b/LaunchServer/source/response/auth/AuthResponse.java @@ -18,16 +18,19 @@ import launchserver.response.Response; import launchserver.response.profile.ProfileByUUIDResponse; -public final class AuthResponse extends Response { +public final class AuthResponse extends Response +{ private final String ip; - public AuthResponse(LaunchServer server, long id, HInput input, HOutput output, String ip) { + public AuthResponse(LaunchServer server, long id, HInput input, HOutput output, String ip) + { super(server, id, input, output); this.ip = ip; } @Override - public void reply() throws Throwable { + public void reply() throws Throwable + { String login = input.readString(255); byte[] encryptedPassword = input.readByteArray(SecurityHelper.CRYPTO_MAX_LENGTH); @@ -45,6 +48,10 @@ debug("Login: '%s', Password: '%s'", login, echo(password.length())); AuthProviderResult result; try { + if (server.limiter.isLimit(ip)) { + AuthProvider.authError(server.config.authRejectString); + return; + } result = server.config.authProvider.auth(login, password, ip); if (!VerifyHelper.isValidUsername(result.username)) { AuthProvider.authError(String.format("Illegal result: '%s'", result.username)); diff --git a/Launcher/source/Launcher.java b/Launcher/source/Launcher.java index fe33310..9f5eb25 100644 --- a/Launcher/source/Launcher.java +++ b/Launcher/source/Launcher.java @@ -77,7 +77,7 @@ private static final AtomicReference CONFIG = new AtomicReference<>(); // Version info - @LauncherAPI public static final String VERSION = "1.3.2"; + @LauncherAPI public static final String VERSION = "1.4.0"; @LauncherAPI public static final String BUILD = readBuildNumber(); @LauncherAPI public static final int PROTOCOL_MAGIC = 0x724724_00 + 23; diff --git a/buildnumber b/buildnumber index f4c68dc..4bd1bbc 100644 --- a/buildnumber +++ b/buildnumber @@ -1 +1 @@ -1.3.2, 25.06.2020 \ No newline at end of file +1.4.0, 25.06.2020 \ No newline at end of file