From 08d5bd1686d021b4ea1d27aa5fef85ca0a2743ab Mon Sep 17 00:00:00 2001 From: LinsaFTW <25271111+linsaftw@users.noreply.github.com> Date: Fri, 4 Mar 2022 13:35:53 -0300 Subject: [PATCH] Antibot System diff --git a/flamecord/pom.xml b/flamecord/pom.xml index f4bf5ec6..4a0d8e5d 100644 --- a/flamecord/pom.xml +++ b/flamecord/pom.xml @@ -30,6 +30,11 @@ ${project.version} compile + + com.maxmind.db + maxmind-db + 2.0.0 + diff --git a/flamecord/src/main/java/dev/_2lstudios/antibot/AccountsCheck.java b/flamecord/src/main/java/dev/_2lstudios/antibot/AccountsCheck.java new file mode 100644 index 00000000..dfee9bf8 --- /dev/null +++ b/flamecord/src/main/java/dev/_2lstudios/antibot/AccountsCheck.java @@ -0,0 +1,36 @@ +package dev._2lstudios.antibot; + +import java.net.SocketAddress; +import java.util.Collection; + +import dev._2lstudios.flamecord.FlameCord; +import dev._2lstudios.flamecord.configuration.FlameCordConfiguration; + +public class AccountsCheck { + private final AddressDataManager addressDataManager; + + public AccountsCheck(final AddressDataManager addressDataManager) { + this.addressDataManager = addressDataManager; + } + + public boolean check(final SocketAddress socketAddress, final String nickname) { + final FlameCordConfiguration config = FlameCord.getInstance().getFlameCordConfiguration(); + + if (config.isAntibotAccountsEnabled()) { + final AddressData addressData = addressDataManager.getAddressData(socketAddress); + final Collection nicknames = addressData.getNicknames(); + + if (nicknames.size() > config.getAntibotAccountsLimit()) { + nicknames.remove(nickname); + + if (config.isAntibotAccountsFirewall()) { + addressData.firewall(); + } + + return true; + } + } + + return false; + } +} diff --git a/flamecord/src/main/java/dev/_2lstudios/antibot/AddressData.java b/flamecord/src/main/java/dev/_2lstudios/antibot/AddressData.java new file mode 100644 index 00000000..cd8220d1 --- /dev/null +++ b/flamecord/src/main/java/dev/_2lstudios/antibot/AddressData.java @@ -0,0 +1,149 @@ +package dev._2lstudios.antibot; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashSet; + +import dev._2lstudios.flamecord.FlameCord; + +public class AddressData { + private final Collection nicknames = new HashSet<>(); + private final String hostString; + private String lastNickname = ""; + private String country = null; + private long lastPing = 0; + private long penultimateConnection = 0; + private long lastConnection = 0; + private long lastFirewall = 0; + private int pingsSecond = 0; + private int totalPings = 0; + private int connectionsSecond = 0; + private int totalConnections = 0; + + public AddressData(final String hostString) { + this.hostString = hostString; + } + + public Collection getNicknames() { + return nicknames; + } + + public String getLastNickname() { + return lastNickname; + } + + public void addNickname(final String nickname) { + if (!lastNickname.equals(nickname)) { + this.lastNickname = nickname; + this.totalConnections = 1; + } + + if (!this.nicknames.contains(nickname)) { + this.nicknames.add(nickname); + } + } + + public long getPenultimateConnection() { + return penultimateConnection; + } + + public long getTimeSincePenultimateConnection() { + return System.currentTimeMillis() - penultimateConnection; + } + + public long getLastConnection() { + return lastConnection; + } + + public long getTimeSinceLastConnection() { + return System.currentTimeMillis() - lastConnection; + } + + private void updatePingsSecond() { + if (System.currentTimeMillis() - lastPing >= 1000) { + pingsSecond = 0; + } + } + + public int getPingsSecond() { + updatePingsSecond(); + return pingsSecond; + } + + public void addPing() { + updatePingsSecond(); + lastPing = System.currentTimeMillis(); + pingsSecond++; + totalPings++; + } + + public int getTotalPings() { + return totalPings; + } + + private void updateConnectionsSecond() { + if (System.currentTimeMillis() - lastConnection >= 1000) { + connectionsSecond = 0; + } + } + + public int getConnectionsSecond() { + updateConnectionsSecond(); + return connectionsSecond; + } + + public void addConnection() { + final long currentTime = System.currentTimeMillis(); + + updateConnectionsSecond(); + penultimateConnection = lastConnection == 0 ? currentTime : lastConnection; + lastConnection = currentTime; + connectionsSecond++; + totalConnections++; + } + + public int getTotalConnections() { + return totalConnections; + } + + public boolean isFirewalled() { + return System.currentTimeMillis() - lastFirewall < FlameCord.getInstance().getFlameCordConfiguration() + .getAntibotFirewallExpire() * 1000; + } + + public String getHostString() { + return hostString; + } + + public void firewall() { + if (!hostString.equals("127.0.0.1")) { + if (FlameCord.getInstance().getFlameCordConfiguration().isAntibotFirewallIpset()) { + Runtime runtime = Runtime.getRuntime(); + + try { + runtime.exec("ipset add flamecord_blacklist " + hostString); + } catch (IOException exception) { + // Ignored + } + } + + this.lastFirewall = System.currentTimeMillis(); + } + } + + public void setTotalConnections(final int totalConnections) { + this.totalConnections = totalConnections; + } + + public String setCountry(final String country) { + return this.country = country; + } + + public String getCountry() { + return country; + } + + public boolean hasNickname(final String nickname) { + return nicknames.contains(nickname); + } +} diff --git a/flamecord/src/main/java/dev/_2lstudios/antibot/AddressDataManager.java b/flamecord/src/main/java/dev/_2lstudios/antibot/AddressDataManager.java new file mode 100644 index 00000000..3f6e4186 --- /dev/null +++ b/flamecord/src/main/java/dev/_2lstudios/antibot/AddressDataManager.java @@ -0,0 +1,25 @@ +package dev._2lstudios.antibot; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.HashMap; +import java.util.Map; + +public class AddressDataManager { + private final Map addressData = new HashMap<>(); + + public AddressData getAddressData(final SocketAddress address) { + final InetSocketAddress iNetSocketAddress = (InetSocketAddress) address; + final String addressString = iNetSocketAddress.getHostString(); + + if (addressData.containsKey(addressString)) { + return addressData.get(addressString); + } else { + AddressData data = new AddressData(addressString); + + addressData.put(addressString, data); + + return data; + } + } +} diff --git a/flamecord/src/main/java/dev/_2lstudios/antibot/CheckManager.java b/flamecord/src/main/java/dev/_2lstudios/antibot/CheckManager.java new file mode 100644 index 00000000..137ed980 --- /dev/null +++ b/flamecord/src/main/java/dev/_2lstudios/antibot/CheckManager.java @@ -0,0 +1,41 @@ +package dev._2lstudios.antibot; + +import dev._2lstudios.flamecord.configuration.FlameCordConfiguration; +import lombok.Getter; + +public class CheckManager { + @Getter + private final AccountsCheck accountsCheck; + @Getter + private final CountryCheck countryCheck; + @Getter + private final FastChatCheck fastChatCheck; + @Getter + private final FirewallCheck firewallCheck; + @Getter + private final NicknameCheck nicknameCheck; + @Getter + private final PasswordCheck passwordCheck; + @Getter + private final RatelimitCheck ratelimitCheck; + @Getter + private final ReconnectCheck reconnectCheck; + + public CheckManager(final AddressDataManager addressDataManager, final FlameCordConfiguration flameCordConfiguration) { + this.accountsCheck = new AccountsCheck(addressDataManager); + this.countryCheck = new CountryCheck(addressDataManager); + this.fastChatCheck = new FastChatCheck(addressDataManager); + this.firewallCheck = new FirewallCheck(addressDataManager, flameCordConfiguration); + this.nicknameCheck = new NicknameCheck(addressDataManager); + this.passwordCheck = new PasswordCheck(addressDataManager); + this.ratelimitCheck = new RatelimitCheck(addressDataManager); + this.reconnectCheck = new ReconnectCheck(addressDataManager); + + this.countryCheck.load(); + this.firewallCheck.load(); + } + + public void unload() { + this.countryCheck.unload(); + } +} diff --git a/flamecord/src/main/java/dev/_2lstudios/antibot/CountryCheck.java b/flamecord/src/main/java/dev/_2lstudios/antibot/CountryCheck.java new file mode 100644 index 00000000..8507e1a7 --- /dev/null +++ b/flamecord/src/main/java/dev/_2lstudios/antibot/CountryCheck.java @@ -0,0 +1,140 @@ +package dev._2lstudios.antibot; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.URL; +import java.nio.file.Files; + +import com.maxmind.db.CHMCache; +import com.maxmind.db.MaxMindDbConstructor; +import com.maxmind.db.MaxMindDbParameter; +import com.maxmind.db.Reader; + +import dev._2lstudios.flamecord.FlameCord; +import dev._2lstudios.flamecord.configuration.FlameCordConfiguration; + +public class CountryCheck { + private final AddressDataManager addressDataManager; + private Reader maxMindReader; + + public CountryCheck(final AddressDataManager addressDataManager) { + this.addressDataManager = addressDataManager; + } + + public void download(final URL url, final File file) throws Exception { + try (InputStream in = url.openStream()) { + Files.copy(in, file.toPath()); + } + } + + public void load() { + final File file = new File("GeoLite2-Country.mmdb"); + + try { + if (!file.exists()) { + System.out.println("Starting download of MaxMindDB (This will take some seconds...)"); + download(new URL("https://git.io/GeoLite2-Country.mmdb"), file); + } + + this.maxMindReader = new Reader(file, new CHMCache()); + } catch (final Exception exception) { + // Ignored + } + } + + public void unload() { + try { + if (this.maxMindReader != null) { + this.maxMindReader.close(); + } + } catch (final IOException ex) { + // Ignored + } + } + + private boolean isBlacklisted(final FlameCordConfiguration config, final String isoCode) { + for (final String blacklisted : config.getAntibotCountryBlacklist()) { + if (isoCode.contains(blacklisted)) { + return true; + } + } + + return false; + } + + public static class LookupResult { + private final Country country; + + @MaxMindDbConstructor + public LookupResult(@MaxMindDbParameter(name = "country") final Country country) { + this.country = country; + } + + public Country getCountry() { + return this.country; + } + } + + public static class Country { + private final String isoCode; + + @MaxMindDbConstructor + public Country(@MaxMindDbParameter(name = "iso_code") final String isoCode) { + this.isoCode = isoCode; + } + + public String getIsoCode() { + return this.isoCode; + } + } + + public String getIsoCode(final InetAddress address) { + try { + final LookupResult lookupResult = maxMindReader.get(address, LookupResult.class); + + if (lookupResult == null) { + return "LOCAL"; + } else { + final Country country = lookupResult.getCountry(); + final String isoCode = country.getIsoCode(); + + return isoCode; + } + } catch (final IOException exception) { + // Ignored + } + + return null; + } + + public boolean check(final SocketAddress socketAddress) { + final FlameCordConfiguration config = FlameCord.getInstance().getFlameCordConfiguration(); + + if (config.isAntibotCountryEnabled()) { + final AddressData addressData = addressDataManager.getAddressData(socketAddress); + final String addressCountry = addressData.getCountry(); + final String country; + + if (addressCountry != null) { + country = addressCountry; + } else { + country = getIsoCode(((InetSocketAddress) socketAddress).getAddress()); + addressData.setCountry(country); + } + + if (country != null && isBlacklisted(config, country)) { + if (config.isAntibotCountryFirewall()) { + addressData.firewall(); + } + + return true; + } + } + + return false; + } +} diff --git a/flamecord/src/main/java/dev/_2lstudios/antibot/FastChatCheck.java b/flamecord/src/main/java/dev/_2lstudios/antibot/FastChatCheck.java new file mode 100644 index 00000000..76902c27 --- /dev/null +++ b/flamecord/src/main/java/dev/_2lstudios/antibot/FastChatCheck.java @@ -0,0 +1,32 @@ +package dev._2lstudios.antibot; + +import java.net.SocketAddress; + +import dev._2lstudios.flamecord.FlameCord; +import dev._2lstudios.flamecord.configuration.FlameCordConfiguration; + +public class FastChatCheck { + private final AddressDataManager addressDataManager; + + public FastChatCheck(final AddressDataManager addressDataManager) { + this.addressDataManager = addressDataManager; + } + + public boolean check(final SocketAddress socketAddress) { + final FlameCordConfiguration config = FlameCord.getInstance().getFlameCordConfiguration(); + + if (config.isAntibotFastChatEnabled()) { + final AddressData addressData = addressDataManager.getAddressData(socketAddress); + + if (addressData.getTimeSinceLastConnection() <= config.getAntibotFastChatTime()) { + if (config.isAntibotFastChatFirewall()) { + addressData.firewall(); + } + + return true; + } + } + + return false; + } +} diff --git a/flamecord/src/main/java/dev/_2lstudios/antibot/FirewallCheck.java b/flamecord/src/main/java/dev/_2lstudios/antibot/FirewallCheck.java new file mode 100644 index 00000000..d3b77f03 --- /dev/null +++ b/flamecord/src/main/java/dev/_2lstudios/antibot/FirewallCheck.java @@ -0,0 +1,42 @@ +package dev._2lstudios.antibot; + +import java.io.IOException; +import java.net.SocketAddress; + +import dev._2lstudios.flamecord.configuration.FlameCordConfiguration; + +public class FirewallCheck { + private final AddressDataManager addressDataManager; + private final FlameCordConfiguration flameCordConfiguration; + + public FirewallCheck(final AddressDataManager addressDataManager, final FlameCordConfiguration flameCordConfiguration) { + this.addressDataManager = addressDataManager; + this.flameCordConfiguration = flameCordConfiguration; + } + + public void load() { + if (flameCordConfiguration.isAntibotFirewallIpset()) { + Runtime runtime = Runtime.getRuntime(); + + try { + runtime.exec("iptables -D INPUT -p tcp -m set --match-set flamecord_blacklist src -j DROP"); + runtime.exec("ipset flush flamecord_blacklist"); + runtime.exec("ipset destroy flamecord_blacklist"); + runtime.exec("ipset create flamecord_blacklist hash:ip timeout " + flameCordConfiguration.getAntibotFirewallExpire()); + runtime.exec("iptables -I INPUT -p tcp -m set --match-set flamecord_blacklist src -j DROP"); + } catch (IOException exception) { + // Ignored + } + } + } + + public boolean check(final SocketAddress socketAddress) { + if (flameCordConfiguration.isAntibotFirewallEnabled()) { + final AddressData addressData = addressDataManager.getAddressData(socketAddress); + + return addressData.isFirewalled(); + } + + return false; + } +} diff --git a/flamecord/src/main/java/dev/_2lstudios/antibot/NicknameCheck.java b/flamecord/src/main/java/dev/_2lstudios/antibot/NicknameCheck.java new file mode 100644 index 00000000..f9b1ad70 --- /dev/null +++ b/flamecord/src/main/java/dev/_2lstudios/antibot/NicknameCheck.java @@ -0,0 +1,43 @@ +package dev._2lstudios.antibot; + +import java.net.SocketAddress; + +import dev._2lstudios.flamecord.FlameCord; +import dev._2lstudios.flamecord.configuration.FlameCordConfiguration; + +public class NicknameCheck { + private AddressDataManager addressDataManager; + + public NicknameCheck(final AddressDataManager addressDataManager) { + this.addressDataManager = addressDataManager; + } + + private boolean isBlacklisted(final FlameCordConfiguration config, final String nickname) { + for (final String blacklisted : config.getAntibotNicknameBlacklist()) { + if (nickname.toLowerCase().contains(blacklisted)) { + return true; + } + } + + return false; + } + + public boolean check(final SocketAddress socketAddress) { + final FlameCordConfiguration config = FlameCord.getInstance().getFlameCordConfiguration(); + + if (config.isAntibotNicknameEnabled()) { + final AddressData addressData = addressDataManager.getAddressData(socketAddress); + final String nickname = addressData.getLastNickname(); + + if (isBlacklisted(config, nickname)) { + if (config.isAntibotNicknameFirewall()) { + addressData.firewall(); + } + + return true; + } + } + + return false; + } +} diff --git a/flamecord/src/main/java/dev/_2lstudios/antibot/PasswordCheck.java b/flamecord/src/main/java/dev/_2lstudios/antibot/PasswordCheck.java new file mode 100644 index 00000000..8ffe1e21 --- /dev/null +++ b/flamecord/src/main/java/dev/_2lstudios/antibot/PasswordCheck.java @@ -0,0 +1,62 @@ +package dev._2lstudios.antibot; + +import java.net.SocketAddress; + +import dev._2lstudios.flamecord.FlameCord; +import dev._2lstudios.flamecord.configuration.FlameCordConfiguration; + +public class PasswordCheck { + private AddressDataManager addressDataManager; + private String lastNickname = ""; + private String lastPassword = ""; + private int repeatCount = 0; + + public PasswordCheck(final AddressDataManager addressDataManager) { + this.addressDataManager = addressDataManager; + } + + private void updatePassword(final FlameCordConfiguration config, final String nickname, final String password) { + if (!nickname.equals(lastNickname)) { + if (password.equals(lastPassword)) { + if (repeatCount < config.getAntibotPasswordLimit()) { + repeatCount++; + } + } else if (repeatCount > 0) { + repeatCount--; + } + } + + lastNickname = nickname; + lastPassword = password; + } + + public boolean check(final SocketAddress socketAddress, final String passwordMessage) { + final FlameCordConfiguration config = FlameCord.getInstance().getFlameCordConfiguration(); + + if (config.isAntibotPasswordEnabled()) { + if (passwordMessage.contains("/login ") || passwordMessage.contains("/l ") + || passwordMessage.contains("/register ") + || passwordMessage.contains("/reg ")) { + final AddressData addressData = addressDataManager.getAddressData(socketAddress); + final String nickname = addressData.getLastNickname(); + final String password = passwordMessage.split(" ")[1]; + + updatePassword(config, nickname, password); + + if (repeatCount >= config.getAntibotPasswordLimit()) { + if (config.isAntibotPasswordFirewall()) { + addressData.firewall(); + } + + return true; + } + } + } + + return false; + } + + public int getRepeatCount() { + return repeatCount; + } +} diff --git a/flamecord/src/main/java/dev/_2lstudios/antibot/RatelimitCheck.java b/flamecord/src/main/java/dev/_2lstudios/antibot/RatelimitCheck.java new file mode 100644 index 00000000..f9ab936b --- /dev/null +++ b/flamecord/src/main/java/dev/_2lstudios/antibot/RatelimitCheck.java @@ -0,0 +1,33 @@ +package dev._2lstudios.antibot; + +import java.net.SocketAddress; + +import dev._2lstudios.flamecord.FlameCord; +import dev._2lstudios.flamecord.configuration.FlameCordConfiguration; + +public class RatelimitCheck { + private final AddressDataManager addressDataManager; + + public RatelimitCheck(final AddressDataManager addressDataManager) { + this.addressDataManager = addressDataManager; + } + + public boolean check(final SocketAddress socketAddress) { + final FlameCordConfiguration config = FlameCord.getInstance().getFlameCordConfiguration(); + + if (config.isAntibotRatelimitEnabled()) { + final AddressData addressData = addressDataManager.getAddressData(socketAddress); + + if (addressData.getConnectionsSecond() >= config.getAntibotRatelimitConnectionsPerSecond() + || addressData.getPingsSecond() >= config.getAntibotRatelimitPingsPerSecond()) { + if (config.isAntibotRatelimitFirewall()) { + addressData.firewall(); + } + + return true; + } + } + + return false; + } +} diff --git a/flamecord/src/main/java/dev/_2lstudios/antibot/ReconnectCheck.java b/flamecord/src/main/java/dev/_2lstudios/antibot/ReconnectCheck.java new file mode 100644 index 00000000..37568225 --- /dev/null +++ b/flamecord/src/main/java/dev/_2lstudios/antibot/ReconnectCheck.java @@ -0,0 +1,46 @@ +package dev._2lstudios.antibot; + +import java.net.SocketAddress; + +import dev._2lstudios.flamecord.FlameCord; +import dev._2lstudios.flamecord.configuration.FlameCordConfiguration; + +public class ReconnectCheck { + private final AddressDataManager addressDataManager; + private int connections = 0; + private long lastConnection = 0; + + public ReconnectCheck(final AddressDataManager addressDataManager) { + this.addressDataManager = addressDataManager; + } + + public boolean check(final SocketAddress socketAddress) { + final FlameCordConfiguration config = FlameCord.getInstance().getFlameCordConfiguration(); + + if (config.isAntibotReconnectEnabled()) { + final long currentTime = System.currentTimeMillis(); + + if (currentTime - lastConnection > config.getAntibotReconnectConnectionThresholdLimit()) { + lastConnection = currentTime; + connections = 0; + } + + if (++connections > config.getAntibotReconnectConnectionThreshold()) { + final AddressData addressData = addressDataManager.getAddressData(socketAddress); + final boolean needsAttempts = addressData.getTotalConnections() < config.getAntibotReconnectAttempts() + || addressData.getTotalPings() < config.getAntibotReconnectPings(); + final boolean tooSlow = addressData.getTimeSincePenultimateConnection() > config + .getAntibotReconnectMaxTime(); + + if (tooSlow) { + addressData.setTotalConnections(0); + return true; + } else { + return needsAttempts; + } + } + } + + return false; + } +} diff --git a/flamecord/src/main/java/dev/_2lstudios/flamecord/FlameCord.java b/flamecord/src/main/java/dev/_2lstudios/flamecord/FlameCord.java index c78ab3a7..4079caa5 100644 --- a/flamecord/src/main/java/dev/_2lstudios/flamecord/FlameCord.java +++ b/flamecord/src/main/java/dev/_2lstudios/flamecord/FlameCord.java @@ -3,6 +3,8 @@ package dev._2lstudios.flamecord; import java.util.Collection; import java.util.logging.Logger; +import dev._2lstudios.antibot.AddressDataManager; +import dev._2lstudios.antibot.CheckManager; import dev._2lstudios.flamecord.configuration.FlameCordConfiguration; import dev._2lstudios.flamecord.configuration.MessagesConfiguration; import dev._2lstudios.flamecord.configuration.ModulesConfiguration; @@ -25,16 +27,24 @@ public class FlameCord { @Getter private FlameCordConfiguration flameCordConfiguration; @Getter + private AddressDataManager addressDataManager; + @Getter + private CheckManager checkManager; + @Getter private ModulesConfiguration modulesConfiguration; @Getter private MessagesConfiguration messagesConfiguration; private void reload(final Logger logger) { final ConfigurationProvider configurationProvider = ConfigurationProvider.getProvider(YamlConfiguration.class); + + if (checkManager != null) checkManager.unload(); this.flameCordConfiguration = new FlameCordConfiguration(configurationProvider); this.modulesConfiguration = new ModulesConfiguration(configurationProvider); this.messagesConfiguration = new MessagesConfiguration(logger, configurationProvider); + this.addressDataManager = new AddressDataManager(); + this.checkManager = new CheckManager(addressDataManager, flameCordConfiguration); } private FlameCord(final Logger logger, final Collection whitelistedAddresses) { diff --git a/flamecord/src/main/java/dev/_2lstudios/flamecord/configuration/FlameCordConfiguration.java b/flamecord/src/main/java/dev/_2lstudios/flamecord/configuration/FlameCordConfiguration.java index 5d92c20c..da3f915f 100644 --- a/flamecord/src/main/java/dev/_2lstudios/flamecord/configuration/FlameCordConfiguration.java +++ b/flamecord/src/main/java/dev/_2lstudios/flamecord/configuration/FlameCordConfiguration.java @@ -16,6 +16,120 @@ import net.md_5.bungee.config.Configuration; import net.md_5.bungee.config.ConfigurationProvider; public class FlameCordConfiguration extends FlameConfig { + // FlameCord start - Antibot System + @Getter + private boolean antibotAccountsEnabled = true; + @Getter + private boolean antibotAccountsFirewall = true; + @Getter + private int antibotAccountsLimit = 3; + @Getter + private boolean antibotAccountsLog = true; + @Getter + private boolean antibotCountryEnabled = true; + @Getter + private boolean antibotCountryFirewall = true; + @Getter + private Collection antibotCountryBlacklist = Arrays.asList("CN", "HK", "RU", "IN", "TH", "ID", "DZ", "VN", "IR", "PK"); + @Getter + private boolean antibotCountryLog = true; + @Getter + private boolean antibotFastChatEnabled = true; + @Getter + private boolean antibotFastChatFirewall = true; + @Getter + private int antibotFastChatTime = 1000; + @Getter + private boolean antibotFastChatLog = true; + @Getter + private boolean antibotFirewallEnabled = true; + @Getter + private boolean antibotFirewallIpset = true; + @Getter + private int antibotFirewallExpire = 60; + @Getter + private boolean antibotFirewallLog = true; + @Getter + private boolean antibotNicknameEnabled = true; + @Getter + private boolean antibotNicknameFirewall = true; + @Getter + private Collection antibotNicknameBlacklist = Arrays.asList("mcstorm", "mcdown", "mcbot", "theresa_bot", "dropbot", "kingbot"); + @Getter + private boolean antibotNicknameLog = true; + @Getter + private boolean antibotPasswordEnabled = true; + @Getter + private boolean antibotPasswordFirewall = true; + @Getter + private int antibotPasswordLimit = 3; + @Getter + private boolean antibotPasswordLog = true; + @Getter + private boolean antibotRatelimitEnabled = true; + @Getter + private boolean antibotRatelimitFirewall = true; + @Getter + private int antibotRatelimitConnectionsPerSecond = 3; + @Getter + private int antibotRatelimitPingsPerSecond = 8; + @Getter + private boolean antibotRatelimitLog = true; + @Getter + private boolean antibotReconnectEnabled = true; + @Getter + private int antibotReconnectAttempts = 2; + @Getter + private int antibotReconnectPings = 0; + @Getter + private int antibotReconnectMaxTime = 10000; + @Getter + private int antibotReconnectConnectionThreshold = 1; + @Getter + private int antibotReconnectConnectionThresholdLimit = 8000; + @Getter + private boolean antibotReconnectLog = true; + + public void loadAntibot(final Configuration config) { + this.antibotAccountsEnabled = setIfUnexistant("antibot.accounts.enabled", this.antibotAccountsEnabled, config); + this.antibotAccountsFirewall = setIfUnexistant("antibot.accounts.firewall", this.antibotAccountsFirewall, config); + this.antibotAccountsLimit = setIfUnexistant("antibot.accounts.limit", this.antibotAccountsLimit, config); + this.antibotAccountsLog = setIfUnexistant("antibot.accounts.log", this.antibotAccountsLog, config); + this.antibotCountryEnabled = setIfUnexistant("antibot.country.enabled", this.antibotCountryEnabled, config); + this.antibotCountryFirewall = setIfUnexistant("antibot.country.firewall", this.antibotCountryFirewall, config); + this.antibotCountryBlacklist = setIfUnexistant("antibot.country.blacklist", this.antibotCountryBlacklist, config); + this.antibotCountryLog = setIfUnexistant("antibot.country.log", this.antibotCountryLog, config); + this.antibotFastChatEnabled = setIfUnexistant("antibot.fastchat.enabled", this.antibotFastChatEnabled, config); + this.antibotFastChatFirewall = setIfUnexistant("antibot.fastchat.firewall", this.antibotFastChatFirewall, config); + this.antibotFastChatTime = setIfUnexistant("antibot.fastchat.time", this.antibotFastChatTime, config); + this.antibotFastChatLog = setIfUnexistant("antibot.fastchat.log", this.antibotFastChatLog, config); + this.antibotFirewallEnabled = setIfUnexistant("antibot.firewall.enabled", this.antibotFirewallEnabled, config); + this.antibotFirewallIpset = setIfUnexistant("antibot.firewall.ipset", this.antibotFirewallIpset, config); + this.antibotFirewallExpire = setIfUnexistant("antibot.firewall.time", this.antibotFirewallExpire, config); + this.antibotFirewallLog = setIfUnexistant("antibot.firewall.log", this.antibotFirewallLog, config); + this.antibotNicknameEnabled = setIfUnexistant("antibot.nickname.enabled", this.antibotNicknameEnabled, config); + this.antibotNicknameFirewall = setIfUnexistant("antibot.nickname.firewall", this.antibotNicknameFirewall, config); + this.antibotNicknameBlacklist = setIfUnexistant("antibot.nickname.blacklist", this.antibotNicknameBlacklist, config); + this.antibotNicknameLog = setIfUnexistant("antibot.nickname.log", this.antibotNicknameLog, config); + this.antibotPasswordEnabled = setIfUnexistant("antibot.password.enabled", this.antibotPasswordEnabled, config); + this.antibotPasswordFirewall = setIfUnexistant("antibot.password.firewall", this.antibotPasswordFirewall, config); + this.antibotPasswordLimit = setIfUnexistant("antibot.password.limit", this.antibotPasswordLimit, config); + this.antibotPasswordLog = setIfUnexistant("antibot.password.log", this.antibotPasswordLog, config); + this.antibotRatelimitEnabled = setIfUnexistant("antibot.ratelimit.enabled", this.antibotRatelimitEnabled, config); + this.antibotRatelimitFirewall = setIfUnexistant("antibot.ratelimit.firewall", this.antibotRatelimitFirewall, config); + this.antibotRatelimitConnectionsPerSecond = setIfUnexistant("antibot.ratelimit.connections-per-second", this.antibotRatelimitConnectionsPerSecond, config); + this.antibotRatelimitPingsPerSecond = setIfUnexistant("antibot.ratelimit.pings-per-second", this.antibotRatelimitPingsPerSecond, config); + this.antibotRatelimitLog = setIfUnexistant("antibot.ratelimit.log", this.antibotRatelimitLog, config); + this.antibotReconnectEnabled = setIfUnexistant("antibot.reconnect.enabled", this.antibotReconnectEnabled, config); + this.antibotReconnectAttempts = setIfUnexistant("antibot.reconnect.attempts", this.antibotReconnectAttempts, config); + this.antibotReconnectPings = setIfUnexistant("antibot.reconnect.pings", this.antibotReconnectPings, config); + this.antibotReconnectMaxTime = setIfUnexistant("antibot.reconnect.max-time", this.antibotReconnectMaxTime, config); + this.antibotReconnectConnectionThreshold = setIfUnexistant("antibot.reconnect.connection-threshold", this.antibotReconnectConnectionThreshold, config); + this.antibotReconnectConnectionThresholdLimit = setIfUnexistant("antibot.reconnect.connection-threshold-limit", this.antibotReconnectConnectionThresholdLimit, config); + this.antibotReconnectLog = setIfUnexistant("antibot.reconnect.log", this.antibotReconnectLog, config); + } + // FlameCord end - Antibot System + // FlameCord - TCP Fast Open @Getter private int tcpFastOpen = 3; @@ -117,6 +231,8 @@ public class FlameCordConfiguration extends FlameConfig { this.fakePlayersEnabled = setIfUnexistant("custom-motd.fakeplayers.enabled", this.fakePlayersEnabled, configuration); this.fakePlayersAmount = setIfUnexistant("custom-motd.fakeplayers.amount", this.fakePlayersAmount, configuration); this.fakePlayersMode = setIfUnexistant("custom-motd.fakeplayers.mode", this.fakePlayersMode, configuration); + loadAntibot(configuration); + this.tcpFastOpen = setIfUnexistant("tcp-fast-open", this.tcpFastOpen, configuration); this.loggerInitialhandler = setIfUnexistant("logger.initialhandler", this.loggerInitialhandler, configuration); diff --git a/flamecord/src/main/java/dev/_2lstudios/flamecord/configuration/MessagesConfiguration.java b/flamecord/src/main/java/dev/_2lstudios/flamecord/configuration/MessagesConfiguration.java index 57462992..ee0295c7 100644 --- a/flamecord/src/main/java/dev/_2lstudios/flamecord/configuration/MessagesConfiguration.java +++ b/flamecord/src/main/java/dev/_2lstudios/flamecord/configuration/MessagesConfiguration.java @@ -81,6 +81,17 @@ public class MessagesConfiguration extends FlameConfig { setIfUnexistant("command_ip", "&9IP of {0} is {1}", configuration); setIfUnexistant("illegal_chat_characters", "&cIllegal characters in chat ({0})", configuration); + // FlameCord start - Antibot System + setIfUnexistant("antibot_accounts", "&c&lFlameCord\n\n&cYou have too many accounts! ({0})\n\n&cError? Contact us on discord.gg/gF36AT3", configuration); + setIfUnexistant("antibot_fastchat", "&c&lFlameCord\n\n&cYou are chatting too fast!\n\n&cError? Contact us on discord.gg/gF36AT3", configuration); + setIfUnexistant("antibot_firewall", "&c&lFlameCord\n\n&cYou are blocked from this server!\n\n&cError? Contact us on discord.gg/gF36AT3", configuration); + setIfUnexistant("antibot_nickname", "&c&lFlameCord\n\n&cYour nickname was detected as bot! ({0})\n\n&cError? Contact us on discord.gg/gF36AT3", configuration); + setIfUnexistant("antibot_password", "&c&lFlameCord\n\n&cYour password is used by other players! ({0})\n\n&cError? Contact us on discord.gg/gF36AT3", configuration); + setIfUnexistant("antibot_ratelimit", "&c&lFlameCord\n\n&cYou are connecting too fast! ({0})\n\n&cError? Contact us on discord.gg/gF36AT3", configuration); + setIfUnexistant("antibot_reconnect", "&c&lFlameCord\n\n&cReconnect {0} more times to enter!\n\n&cError? Contact us on discord.gg/gF36AT3", configuration); + setIfUnexistant("antibot_country", "&c&lFlameCord\n\n&cYour country {0} is blacklisted!\n\n&cError? Contact us on discord.gg/gF36AT3", configuration); + // FlameCord end - Antibot System + // FlameCord setIfUnexistant("flamecord_reload", "&aAll files had been successfully reloaded!", configuration); setIfUnexistant("flamecord_help", diff --git a/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java b/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java index 0a09f92c..f20acb2f 100644 --- a/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java +++ b/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java @@ -19,6 +19,8 @@ import java.util.logging.Level; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; +import dev._2lstudios.antibot.AddressData; +import dev._2lstudios.antibot.CheckManager; import dev._2lstudios.flamecord.FlameCord; import dev._2lstudios.flamecord.configuration.FlameConfig; @@ -415,6 +417,11 @@ public class InitialHandler extends PacketHandler implements PendingConnection return; } + // FlameCord start - Antibot System + AddressData addressData = FlameCord.getInstance().getAddressDataManager().getAddressData( ch.getRemoteAddress() ); + CheckManager checkManager = FlameCord.getInstance().getCheckManager(); + // FlameCord end - Antibot System + switch ( handshake.getRequestedProtocol() ) { case 1: @@ -426,6 +433,22 @@ public class InitialHandler extends PacketHandler implements PendingConnection } thisState = State.STATUS; ch.setProtocol( Protocol.STATUS ); + + // FlameCord start - Antibot System + addressData.addPing(); + + if ( checkManager.getRatelimitCheck().check( ch.getRemoteAddress() ) ) + { + if ( FlameCord.getInstance().getFlameCordConfiguration().isAntibotRatelimitLog() ) + { + bungee.getLogger().log( Level.INFO, "[FlameCord] [{0}] is pinging too fast", ch.getRemoteAddress() ); + } + + disconnect( bungee.getTranslation( "antibot_ratelimit", addressData.getPingsSecond() ) ); + return; + } + // FlameCord end - Antibot System + break; case 2: // Login @@ -437,6 +460,21 @@ public class InitialHandler extends PacketHandler implements PendingConnection thisState = State.USERNAME; ch.setProtocol( Protocol.LOGIN ); + // FlameCord start - Antibot System + addressData.addConnection(); + + if ( checkManager.getRatelimitCheck().check( ch.getRemoteAddress() ) ) + { + if ( FlameCord.getInstance().getFlameCordConfiguration().isAntibotRatelimitLog() ) + { + bungee.getLogger().log( Level.INFO, "[FlameCord] [{0}] is connecting too fast", ch.getRemoteAddress() ); + } + + disconnect( bungee.getTranslation( "antibot_ratelimit", addressData.getConnectionsSecond() ) ); + return; + } + // FlameCord end - Antibot System + if ( !ProtocolConstants.SUPPORTED_VERSION_IDS.contains( handshake.getProtocolVersion() ) ) { if ( handshake.getProtocolVersion() > bungee.getProtocolVersion() ) @@ -474,6 +512,58 @@ public class InitialHandler extends PacketHandler implements PendingConnection return; } + // FlameCord start - Antibot System + CheckManager checkManager = FlameCord.getInstance().getCheckManager(); + AddressData addressData = FlameCord.getInstance().getAddressDataManager().getAddressData( ch.getRemoteAddress() ); + String nickname = loginRequest.getData(); + + addressData.addNickname( nickname ); + + if ( checkManager.getAccountsCheck().check( ch.getRemoteAddress(), nickname ) ) + { + if ( FlameCord.getInstance().getFlameCordConfiguration().isAntibotAccountsLog() ) + { + bungee.getLogger().log( Level.INFO, "[FlameCord] [{0}] has too many accounts", ch.getRemoteAddress() ); + } + + disconnect( bungee.getTranslation( "antibot_accounts", addressData.getNicknames().size() ) ); + return; + } + + if ( checkManager.getNicknameCheck().check( ch.getRemoteAddress() ) ) + { + if ( FlameCord.getInstance().getFlameCordConfiguration().isAntibotNicknameLog() ) + { + bungee.getLogger().log( Level.INFO, "[FlameCord] [{0}] has a blacklisted nickname", ch.getRemoteAddress() ); + } + + disconnect( bungee.getTranslation( "antibot_nickname", loginRequest.getData() ) ); + return; + } + + if ( checkManager.getReconnectCheck().check( ch.getRemoteAddress() ) ) + { + if ( FlameCord.getInstance().getFlameCordConfiguration().isAntibotReconnectLog() ) + { + bungee.getLogger().log( Level.INFO, "[FlameCord] [{0}] has to reconnect to join", ch.getRemoteAddress() ); + } + + disconnect( bungee.getTranslation( "antibot_reconnect", FlameCord.getInstance().getFlameCordConfiguration().getAntibotReconnectAttempts() - addressData.getTotalConnections() ) ); + return; + } + + if ( checkManager.getCountryCheck().check( ch.getRemoteAddress() ) ) + { + if ( FlameCord.getInstance().getFlameCordConfiguration().isAntibotCountryLog() ) + { + bungee.getLogger().log( Level.INFO, "[FlameCord] [{0}] has his country blocked from the server", ch.getRemoteAddress() ); + } + + disconnect( bungee.getTranslation( "antibot_country", addressData.getCountry() ) ); + return; + } + // FlameCord end - Antibot System + // If offline mode and they are already on, don't allow connect // We can just check by UUID here as names are based on UUID if ( !isOnlineMode() && bungee.getPlayer( getUniqueId() ) != null ) diff --git a/proxy/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java b/proxy/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java index e354032a..f5fc53bc 100644 --- a/proxy/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java +++ b/proxy/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java @@ -4,10 +4,15 @@ import com.google.common.base.Preconditions; import com.mojang.brigadier.context.StringRange; import com.mojang.brigadier.suggestion.Suggestion; import com.mojang.brigadier.suggestion.Suggestions; + +import dev._2lstudios.antibot.CheckManager; +import dev._2lstudios.flamecord.FlameCord; import io.netty.channel.Channel; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +import java.util.logging.Level; + import net.md_5.bungee.BungeeCord; import net.md_5.bungee.ServerConnection.KeepAliveData; import net.md_5.bungee.UserConnection; @@ -166,6 +171,32 @@ public class UpstreamBridge extends PacketHandler } Preconditions.checkArgument(!empty, "Chat message is empty"); + // FlameCord start - Antibot System + final CheckManager checkManager = FlameCord.getInstance().getCheckManager(); + + if ( checkManager.getFastChatCheck().check( con.getCh().getRemoteAddress() ) ) + { + if ( FlameCord.getInstance().getFlameCordConfiguration().isAntibotFastChatLog() ) + { + bungee.getLogger().log( Level.INFO, "[FlameCord] [{0}] is chatting too fast", con.getCh().getRemoteAddress() ); + } + + con.disconnect( bungee.getTranslation( "antibot_fastchat" ) ); + throw CancelSendSignal.INSTANCE; + } + + if ( checkManager.getPasswordCheck().check( con.getCh().getRemoteAddress(), chat.getMessage() ) ) + { + if ( FlameCord.getInstance().getFlameCordConfiguration().isAntibotPasswordLog() ) + { + bungee.getLogger().log( Level.INFO, "[FlameCord] [{0}] has entered a repeated password", con.getCh().getRemoteAddress() ); + } + + con.disconnect( bungee.getTranslation( "antibot_password", checkManager.getPasswordCheck().getRepeatCount() ) ); + throw CancelSendSignal.INSTANCE; + } + // FlameCord end - Antibot System + ChatEvent chatEvent = new ChatEvent( con, con.getServer(), chat.getMessage() ); if ( !bungee.getPluginManager().callEvent( chatEvent ).isCancelled() ) { diff --git a/proxy/src/main/java/net/md_5/bungee/netty/HandlerBoss.java b/proxy/src/main/java/net/md_5/bungee/netty/HandlerBoss.java index 20c4c0a4..a2086b45 100644 --- a/proxy/src/main/java/net/md_5/bungee/netty/HandlerBoss.java +++ b/proxy/src/main/java/net/md_5/bungee/netty/HandlerBoss.java @@ -152,6 +152,13 @@ public class HandlerBoss extends ChannelInboundHandlerAdapter { boolean logExceptions = !( handler instanceof PingHandler ); + // Flamecord start - Antibot System + if (!(cause instanceof ReadTimeoutException)) + { + FlameCord.getInstance().getAddressDataManager().getAddressData(ctx.channel().remoteAddress()).firewall(); + } + // Flamecord end - Antibot System + // FlameCord - Option to log exceptions logExceptions = FlameCord.getInstance().getFlameCordConfiguration().isLoggerExceptions() ? logExceptions : false; diff --git a/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java b/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java index ffea1599..6bf109f5 100644 --- a/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java +++ b/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java @@ -59,6 +59,19 @@ public class PipelineUtils { SocketAddress remoteAddress = ( ch.remoteAddress() == null ) ? ch.parent().localAddress() : ch.remoteAddress(); + // FlameCord start - Antibot System + if ( FlameCord.getInstance().getCheckManager().getFirewallCheck().check( ch.remoteAddress() ) ) + { + if ( FlameCord.getInstance().getFlameCordConfiguration().isAntibotFirewallLog() ) + { + BungeeCord.getInstance().getLogger().log( Level.INFO, "[FlameCord] [{0}] is firewalled from the server", ch.remoteAddress() ); + } + + ch.close(); + return; + } + // FlameCord end - Antibot System + if ( BungeeCord.getInstance().getConnectionThrottle() != null && BungeeCord.getInstance().getConnectionThrottle().throttle( remoteAddress ) ) { ch.close(); -- 2.34.1