aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPetr Ilin <hevav@hevav.dev>2022-12-25 21:49:37 +0300
committerPetr Ilin <hevav@hevav.dev>2022-12-25 21:49:37 +0300
commit9fa813367fb4d94d499c40e478da55523f9915b7 (patch)
tree23231858cf069bc8a081e57fe291c95906172fdb
parent373aebdc16ecd193a716a7afdea17b1dc094fed4 (diff)
downloadLimboAuth-9fa813367fb4d94d499c40e478da55523f9915b7.tar.gz
LimboAuth-9fa813367fb4d94d499c40e478da55523f9915b7.tar.bz2
LimboAuth-9fa813367fb4d94d499c40e478da55523f9915b7.zip
Ban IP on bruteforce attempts
-rw-r--r--src/main/java/net/elytrium/limboauth/LimboAuth.java58
-rw-r--r--src/main/java/net/elytrium/limboauth/Settings.java3
-rw-r--r--src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java12
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);