diff options
author | Petr Ilin <hevav@hevav.dev> | 2022-12-25 21:49:37 +0300 |
---|---|---|
committer | Petr Ilin <hevav@hevav.dev> | 2022-12-25 21:49:37 +0300 |
commit | 9fa813367fb4d94d499c40e478da55523f9915b7 (patch) | |
tree | 23231858cf069bc8a081e57fe291c95906172fdb | |
parent | 373aebdc16ecd193a716a7afdea17b1dc094fed4 (diff) | |
download | LimboAuth-9fa813367fb4d94d499c40e478da55523f9915b7.tar.gz LimboAuth-9fa813367fb4d94d499c40e478da55523f9915b7.tar.bz2 LimboAuth-9fa813367fb4d94d499c40e478da55523f9915b7.zip |
Ban IP on bruteforce attempts
3 files changed, 72 insertions, 1 deletions
diff --git a/src/main/java/net/elytrium/limboauth/LimboAuth.java b/src/main/java/net/elytrium/limboauth/LimboAuth.java index 9eaef13..e18b2bf 100644 --- a/src/main/java/net/elytrium/limboauth/LimboAuth.java +++ b/src/main/java/net/elytrium/limboauth/LimboAuth.java @@ -134,6 +134,7 @@ public class LimboAuth { private final Map<String, CachedSessionUser> cachedAuthChecks = new ConcurrentHashMap<>(); private final Map<String, CachedPremiumUser> premiumCache = new ConcurrentHashMap<>(); + private final Map<InetAddress, CachedBruteforceUser> bruteforceCache = new ConcurrentHashMap<>(); private final Map<UUID, Runnable> postLoginTasks = new ConcurrentHashMap<>(); private final Set<String> unsafePasswords = new HashSet<>(); private final Set<String> forcedPreviously = Collections.synchronizedSet(new HashSet<>()); @@ -157,10 +158,12 @@ public class LimboAuth { @Nullable private Title loginFloodgateTitle; private Component registrationsDisabledKick; + private Component bruteforceAttemptKick; private Component nicknameInvalidKick; private Component reconnectKick; private ScheduledTask purgeCacheTask; private ScheduledTask purgePremiumCacheTask; + private ScheduledTask purgeBruteforceCacheTask; private ConnectionSource connectionSource; private Dao<RegisteredPlayer, String> playerDao; @@ -253,6 +256,7 @@ public class LimboAuth { ); } + this.bruteforceAttemptKick = SERIALIZER.deserialize(Settings.IMP.MAIN.STRINGS.LOGIN_WRONG_PASSWORD_KICK); this.nicknameInvalidKick = SERIALIZER.deserialize(Settings.IMP.MAIN.STRINGS.NICKNAME_INVALID_KICK); this.reconnectKick = SERIALIZER.deserialize(Settings.IMP.MAIN.STRINGS.RECONNECT_KICK); this.registrationsDisabledKick = SERIALIZER.deserialize(Settings.IMP.MAIN.STRINGS.REGISTRATIONS_DISABLED_KICK); @@ -270,6 +274,8 @@ public class LimboAuth { } this.cachedAuthChecks.clear(); + this.premiumCache.clear(); + this.bruteforceCache.clear(); Settings.DATABASE dbConfig = Settings.IMP.DATABASE; DatabaseLibrary databaseLibrary = DatabaseLibrary.valueOf(dbConfig.STORAGE_TYPE.toUpperCase(Locale.ROOT)); @@ -383,6 +389,16 @@ public class LimboAuth { .repeat(Settings.IMP.MAIN.PURGE_PREMIUM_CACHE_MILLIS, TimeUnit.MILLISECONDS) .schedule(); + if (this.purgeBruteforceCacheTask != null) { + this.purgeBruteforceCacheTask.cancel(); + } + + this.purgeBruteforceCacheTask = this.server.getScheduler() + .buildTask(this, () -> this.checkCache(this.bruteforceCache, Settings.IMP.MAIN.PURGE_BRUTEFORCE_CACHE_MILLIS)) + .delay(Settings.IMP.MAIN.PURGE_BRUTEFORCE_CACHE_MILLIS, TimeUnit.MILLISECONDS) + .repeat(Settings.IMP.MAIN.PURGE_BRUTEFORCE_CACHE_MILLIS, TimeUnit.MILLISECONDS) + .schedule(); + eventManager.fireAndForget(new AuthPluginReloadEvent()); } @@ -390,7 +406,7 @@ public class LimboAuth { return commands.stream().filter(command -> command.startsWith("/")).map(command -> command.substring(1)).collect(Collectors.toList()); } - private void checkCache(Map<String, ? extends CachedUser> userMap, long time) { + private void checkCache(Map<?, ? extends CachedUser> userMap, long time) { userMap.entrySet().stream() .filter(userEntry -> userEntry.getValue().getCheckTime() + time <= System.currentTimeMillis()) .map(Map.Entry::getKey) @@ -480,6 +496,11 @@ public class LimboAuth { return; } + if (this.getBruteforceAttempts(player.getRemoteAddress().getAddress()) >= Settings.IMP.MAIN.BRUTEFORCE_MAX_ATTEMPTS) { + player.disconnect(this.bruteforceAttemptKick); + return; + } + boolean isFloodgate = !Settings.IMP.MAIN.FLOODGATE_NEED_AUTH && this.floodgateApi.isFloodgatePlayer(player.getUniqueId()); String nickname = player.getUsername(); if (!this.nicknameValidationPattern.matcher((isFloodgate) ? nickname.substring(this.floodgateApi.getPrefixLength()) : nickname).matches()) { @@ -788,6 +809,24 @@ public class LimboAuth { } } + public void incrementBruteforceAttempts(InetAddress address) { + this.bruteforceCache.get(address).incrementAttempts(); + } + + public int getBruteforceAttempts(InetAddress address) { + CachedBruteforceUser user = this.bruteforceCache.get(address); + if (user == null) { + user = new CachedBruteforceUser(System.currentTimeMillis()); + this.bruteforceCache.put(address, user); + } + + return user.getAttempts(); + } + + public void clearBruteforceAttempts(InetAddress address) { + this.bruteforceCache.remove(address); + } + public void saveForceOfflineMode(String nickname) { this.forcedPreviously.add(nickname); } @@ -881,6 +920,23 @@ public class LimboAuth { } } + private static class CachedBruteforceUser extends CachedUser { + + private int attempts; + + public CachedBruteforceUser(long checkTime) { + super(checkTime); + } + + public void incrementAttempts() { + this.attempts++; + } + + public int getAttempts() { + return this.attempts; + } + } + public static class PremiumResponse { private final PremiumState state; diff --git a/src/main/java/net/elytrium/limboauth/Settings.java b/src/main/java/net/elytrium/limboauth/Settings.java index 0dac249..2181f7b 100644 --- a/src/main/java/net/elytrium/limboauth/Settings.java +++ b/src/main/java/net/elytrium/limboauth/Settings.java @@ -112,6 +112,9 @@ public class Settings extends YamlConfig { public String DIMENSION = "THE_END"; public long PURGE_CACHE_MILLIS = 3600000; public long PURGE_PREMIUM_CACHE_MILLIS = 28800000; + public long PURGE_BRUTEFORCE_CACHE_MILLIS = 28800000; + @Comment("Used to ban IPs when a possible attacker incorrectly enters the password") + public int BRUTEFORCE_MAX_ATTEMPTS = 10; @Comment("QR Generator URL, set {data} placeholder") public String QR_GENERATOR_URL = "https://api.qrserver.com/v1/create-qr-code/?data={data}&size=200x200&ecc=M&margin=30"; public String TOTP_ISSUER = "LimboAuth by Elytrium"; diff --git a/src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java b/src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java index 2ea17e0..9c6185f 100644 --- a/src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java +++ b/src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java @@ -240,6 +240,7 @@ public class AuthSessionHandler implements LimboSessionHandler { } } else if (--this.attempts != 0) { this.proxyPlayer.sendMessage(loginWrongPassword[this.attempts - 1]); + this.checkBruteforceAttempts(); } else { this.proxyPlayer.disconnect(loginWrongPasswordKick); } @@ -249,6 +250,8 @@ public class AuthSessionHandler implements LimboSessionHandler { if (TOTP_CODE_VERIFIER.isValidCode(this.playerInfo.getTotpToken(), args[1])) { this.finishLogin(); return; + } else { + this.checkBruteforceAttempts(); } } } @@ -256,6 +259,13 @@ public class AuthSessionHandler implements LimboSessionHandler { this.sendMessage(false); } + private void checkBruteforceAttempts() { + this.plugin.incrementBruteforceAttempts(this.proxyPlayer.getRemoteAddress().getAddress()); + if (this.plugin.getBruteforceAttempts(this.proxyPlayer.getRemoteAddress().getAddress()) >= Settings.IMP.MAIN.BRUTEFORCE_MAX_ATTEMPTS) { + this.proxyPlayer.disconnect(loginWrongPasswordKick); + } + } + private void saveTempPassword(String password) { this.tempPassword = password; } @@ -333,6 +343,8 @@ public class AuthSessionHandler implements LimboSessionHandler { this.proxyPlayer.showTitle(loginSuccessfulTitle); } + this.plugin.clearBruteforceAttempts(this.proxyPlayer.getRemoteAddress().getAddress()); + this.plugin.getServer().getEventManager() .fire(new PostAuthorizationEvent(this::finishAuth, this.player, this.playerInfo, this.tempPassword)) .thenAcceptAsync(this::finishAuth); |