├── .gitignore ├── INSTALLATION.md ├── LICENSE.txt ├── README.md ├── SECURITY.md ├── bungeeguard-backend ├── pom.xml └── src │ └── main │ └── java │ └── me │ └── lucko │ └── bungeeguard │ └── backend │ ├── BungeeGuardBackend.java │ ├── TokenStore.java │ └── listener │ └── AbstractHandshakeListener.java ├── bungeeguard-bungee ├── pom.xml └── src │ └── main │ ├── java │ └── me │ │ └── lucko │ │ └── bungeeguard │ │ └── bungee │ │ ├── BungeeGuardProxyPlugin.java │ │ └── SpoofedLoginResult.java │ └── resources │ └── bungee.yml ├── bungeeguard-spigot-legacy ├── pom.xml └── src │ └── main │ └── java │ └── me │ └── lucko │ └── bungeeguard │ └── spigot │ └── LegacyProtocolKick.java ├── bungeeguard-spigot ├── pom.xml └── src │ └── main │ ├── java │ └── me │ │ └── lucko │ │ └── bungeeguard │ │ └── spigot │ │ ├── BungeeCordHandshake.java │ │ ├── BungeeGuardBackendPlugin.java │ │ └── listener │ │ ├── PaperHandshakeListener.java │ │ └── ProtocolHandshakeListener.java │ └── resources │ ├── config.yml │ └── plugin.yml ├── bungeeguard-sponge ├── pom.xml └── src │ └── main │ ├── java-templates │ └── me │ │ └── lucko │ │ └── bungeeguard │ │ └── sponge │ │ └── BungeeGuardVersion.java │ ├── java │ └── me │ │ └── lucko │ │ └── bungeeguard │ │ └── sponge │ │ ├── BungeeGuardSponge.java │ │ └── HandshakeListener.java │ └── resources │ └── bungeeguard.conf ├── bungeeguard-universal └── pom.xml └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/ 2 | 3 | ### Intellij ### 4 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 5 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 6 | 7 | # User-specific stuff: 8 | .idea/ 9 | *.iws 10 | /out/ 11 | *.iml 12 | .idea_modules/ 13 | 14 | # JIRA plugin 15 | atlassian-ide-plugin.xml 16 | 17 | # Crashlytics plugin (for Android Studio and IntelliJ) 18 | com_crashlytics_export_strings.xml 19 | crashlytics.properties 20 | crashlytics-build.properties 21 | fabric.properties 22 | 23 | 24 | ### Maven ### 25 | target/ 26 | pom.xml.tag 27 | pom.xml.releaseBackup 28 | pom.xml.versionsBackup 29 | pom.xml.next 30 | release.properties 31 | dependency-reduced-pom.xml 32 | buildNumber.properties 33 | .mvn/timing.properties 34 | 35 | 36 | ### Eclipse ### 37 | 38 | .metadata 39 | bin/ 40 | tmp/ 41 | *.tmp 42 | *.bak 43 | *.swp 44 | *~.nib 45 | local.properties 46 | .settings/ 47 | .loadpath 48 | .recommenders 49 | 50 | # Eclipse Core 51 | .project 52 | 53 | # External tool builders 54 | .externalToolBuilders/ 55 | 56 | # Locally stored "Eclipse launch configurations" 57 | *.launch 58 | 59 | # PyDev specific (Python IDE for Eclipse) 60 | *.pydevproject 61 | 62 | # CDT-specific (C/C++ Development Tooling) 63 | .cproject 64 | 65 | # JDT-specific (Eclipse Java Development Tools) 66 | .classpath 67 | 68 | # Java annotation processor (APT) 69 | .factorypath 70 | 71 | # PDT-specific (PHP Development Tools) 72 | .buildpath 73 | 74 | # sbteclipse plugin 75 | .target 76 | 77 | # Tern plugin 78 | .tern-project 79 | 80 | # TeXlipse plugin 81 | .texlipse 82 | 83 | # STS (Spring Tool Suite) 84 | .springBeans 85 | 86 | # Code Recommenders 87 | .recommenders/ 88 | 89 | 90 | ### Linux ### 91 | *~ 92 | 93 | # temporary files which can be created if a process still has a handle open of a deleted file 94 | .fuse_hidden* 95 | 96 | # KDE directory preferences 97 | .directory 98 | 99 | # Linux trash folder which might appear on any partition or disk 100 | .Trash-* 101 | 102 | # .nfs files are created when an open file is removed but is still being accessed 103 | .nfs* 104 | 105 | 106 | ### macOS ### 107 | *.DS_Store 108 | .AppleDouble 109 | .LSOverride 110 | 111 | # Icon must end with two \r 112 | Icon 113 | # Thumbnails 114 | ._* 115 | # Files that might appear in the root of a volume 116 | .DocumentRevisions-V100 117 | .fseventsd 118 | .Spotlight-V100 119 | .TemporaryItems 120 | .Trashes 121 | .VolumeIcon.icns 122 | .com.apple.timemachine.donotpresent 123 | # Directories potentially created on remote AFP share 124 | .AppleDB 125 | .AppleDesktop 126 | Network Trash Folder 127 | Temporary Items 128 | .apdisk 129 | 130 | 131 | ### Windows ### 132 | # Windows image file caches 133 | Thumbs.db 134 | ehthumbs.db 135 | 136 | # Folder config file 137 | Desktop.ini 138 | 139 | # Recycle Bin used on file shares 140 | $RECYCLE.BIN/ 141 | 142 | # Windows Installer files 143 | *.cab 144 | *.msi 145 | *.msm 146 | *.msp 147 | 148 | # Windows shortcuts 149 | *.lnk 150 | 151 | 152 | ### Java ### 153 | *.class 154 | 155 | # Mobile Tools for Java (J2ME) 156 | .mtj.tmp/ 157 | 158 | # Package Files # 159 | *.jar 160 | *.war 161 | *.ear 162 | 163 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 164 | hs_err_pid* -------------------------------------------------------------------------------- /INSTALLATION.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | Installation is very straightforward. 4 | 5 | If you have access to the underlying system and are able to setup firewall rules using iptables (or otherwise), I strongly recommend you do so. Then, install BungeeGuard as well. 6 | 7 | The latest versions of BungeeGuard can be found from: 8 | 9 | * [GitHub](https://github.com/lucko/BungeeGuard/releases) 10 | * [SpigotMC](https://www.spigotmc.org/resources/bungeeguard.79601/) 11 | * [Jenkins](https://ci.lucko.me/job/BungeeGuard/) 12 | 13 | ### On your proxy... 14 | 15 | #### If you are using BungeeCord 16 | 17 | 1. Ensure `ip_forward` is set to `true` in BungeeCord's `config.yml`. 18 | 2. Add `BungeeGuard.jar` to the plugins folder. Then restart the proxy. If you have multiple proxies in your network, do this for each of them. 19 | 3. Navigate to `/plugins/BungeeGuard/token.yml` and make a note of the token. 20 | 21 | #### If you are using Velocity 22 | 23 | 1. Ensure you are using Velocity 1.1.0 or newer. (There is no need to install `BungeeGuard.jar` - it is built into Velocity already!) 24 | 2. Set `player-info-forwarding-mode` to `"bungeeguard"` in `velocity.toml`, and make note of the `forwarding-secret`. This is the value used for the BungeeGuard token. If you have multiple proxies in your network, do this for each of them. 25 | 4. Restart the proxy. 26 | 27 | ### On each of your backend Minecraft servers... 28 | 29 | 1. Ensure you are either using [Paper](https://papermc.io/) 1.9.4+ or have [ProtocolLib](https://www.spigotmc.org/resources/protocollib.1997/) installed. 30 | 2. Ensure the `bungeecord` setting is set to `true` in `spigot.yml`. 31 | 3. Add `BungeeGuard.jar` to the plugins folder. Then restart the server. 32 | 4. Navigate to `/plugins/BungeeGuard/config.yml`. Add the token(s) generated by the proxy(ies) to the `allowed-tokens` list. 33 | > e.g. 34 | > ```yml 35 | > # Allowed authentication tokens. 36 | > allowed-tokens: 37 | > - "AUSXEwebkOGVnbihJM8gBS0QUutDzvIG009xoAfo1Huba9pGvhfjrA21r8dWVsa8" 38 | > ``` 39 | > **Please make sure you remove the default tokens, so the only values in the list are your allowed tokens.** 40 | 5. Run `bungeeguard reload` from console. 41 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License. 2 | 3 | Copyright (c) lucko (Luck) 4 | Copyright (c) contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 💂 BungeeGuard 2 | 3 | BungeeGuard is a plugin-based security/firewall solution for [BungeeCord](https://www.spigotmc.org/wiki/bungeecord/) (and [Velocity](https://velocitypowered.com/)) proxies. 4 | 5 | * [Download](https://github.com/lucko/BungeeGuard/releases) 6 | * [Development Builds (Jenkins)](https://ci.lucko.me/job/BungeeGuard/) 7 | * [Install Guide](INSTALLATION.md) 8 | 9 | ## The problem 10 | 11 | BungeeCord installations are **insecure by default**, and require additional firewall rules to be configured (using iptables or otherwise) to prevent malicious users from bypassing the proxy and connecting using any uuid/username they choose. 12 | 13 | This is a **well-known issue**, and over the years many (even large) servers have been successfully targeted using this attack. 14 | 15 | ### The conventional solution 16 | 17 | The conventional solution recommended by the BungeeCord author is to configure a firewall rule using iptables or ufw to prevent outside connections to the backend servers. 18 | 19 | However, there are two main problems with this: 20 | 21 | 1. Configuring these firewall rules is complicated, especially for inexperienced users. 22 | 1. Even experienced users sometimes make mistakes or overlook things. Unless the setup is absolutely perfect, rules are prone to being broken during later changes, or reset on system reboot. 23 | 2. Users on "shared hosting" do not have access to the underlying system and most likely cannot setup their own firewall rules. 24 | 25 | ### The BungeeGuard solution 26 | 27 | Server admins install BungeeGuard (just an ordinary plugin!) on their proxies and backend servers. 28 | 29 | * On the **proxy**, BungeeGuard adds a secret "authentication token" to the login handshake. 30 | * On the **backend** (Spigot etc. server), BungeeGuard checks login handshakes to ensure they contain an allowed authentication token. 31 | 32 | It's really that simple. 33 | 34 | ## Installation 35 | 36 | Installation is very straightforward. 37 | 38 | If you have access to the underlying system and are able to setup firewall rules using iptables (or otherwise), I strongly recommend you do so. Then, install BungeeGuard as well. 39 | 40 | See [INSTALLATION.md](INSTALLATION.md) for a detailed install guide. 41 | 42 | ## License 43 | 44 | BungeeGuard is licensed and made available under the permissive MIT license. Please see [LICENSE.txt](LICENSE.txt) for more information. 45 | 46 | Details about vulnerability reporting & security disclosures can be found in [SECURITY.md](SECURITY.md). 47 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting a Vulnerability 2 | 3 | Serious vulnerabilities should be reported in private, using the contact details [here](https://lucko.me/). You can encrypt your message using my PGP key if you feel inclined to do so. 4 | 5 | # Security Patches 6 | 7 | This page will be updated with any notices about security issues in BungeeGuard. 8 | 9 | #### #001 - 7th June 2020 10 | * `v1.2.0` released which fixes a security issue in the BungeeGuard Spigot plugin. 11 | * The issue allowed malicious users to bypass BungeeGuard's authentication checks. 12 | * All releases prior to `1.2` are affected. 13 | 14 | #### #002 - 2nd June 2025 15 | * `v1.4.0` released which fixes a security issue in the BungeeGuard BungeeCord plugin. 16 | * An issue introduced in BungeeCord build 1756 caused the BungeeGuard token to be leaked to players using Minecraft 1.20.2 or higher via the LoginSuccess packet. 17 | * This issue only affects BungeeGuard setups using BungeeCord, it does not affect Velocity proxies. 18 | * Affected users are recommended to update to BungeeGuard `v1.4.0` or later on their proxy, and rotate their BungeeGuard tokens. 19 | -------------------------------------------------------------------------------- /bungeeguard-backend/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | me.lucko 9 | bungeeguard 10 | 1.4-SNAPSHOT 11 | 12 | 13 | bungeeguard-backend 14 | 15 | 16 | -------------------------------------------------------------------------------- /bungeeguard-backend/src/main/java/me/lucko/bungeeguard/backend/BungeeGuardBackend.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of BungeeGuard, licensed under the MIT License. 3 | * 4 | * Copyright (c) lucko (Luck) 5 | * Copyright (c) contributors 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | package me.lucko.bungeeguard.backend; 27 | 28 | import java.util.List; 29 | 30 | public interface BungeeGuardBackend { 31 | 32 | String getMessage(String key); 33 | 34 | List getTokens(); 35 | 36 | void reloadConfig(); 37 | } 38 | -------------------------------------------------------------------------------- /bungeeguard-backend/src/main/java/me/lucko/bungeeguard/backend/TokenStore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of BungeeGuard, licensed under the MIT License. 3 | * 4 | * Copyright (c) lucko (Luck) 5 | * Copyright (c) contributors 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | package me.lucko.bungeeguard.backend; 27 | 28 | import java.util.Collections; 29 | import java.util.HashSet; 30 | import java.util.Set; 31 | 32 | /** 33 | * A store of allowed tokens. 34 | */ 35 | public class TokenStore { 36 | private final BungeeGuardBackend plugin; 37 | private Set allowedTokens = Collections.emptySet(); 38 | 39 | public TokenStore(BungeeGuardBackend plugin) { 40 | this.plugin = plugin; 41 | } 42 | 43 | public void reload() { 44 | this.plugin.reloadConfig(); 45 | load(); 46 | } 47 | 48 | public void load() { 49 | this.allowedTokens = new HashSet<>(this.plugin.getTokens()); 50 | } 51 | 52 | /** 53 | * Gets if a token is allowed. 54 | * 55 | * @param token the token 56 | * @return true if allowed 57 | */ 58 | public boolean isAllowed(String token) { 59 | return this.allowedTokens.contains(token); 60 | } 61 | 62 | /** 63 | * Has the server owner bothered to configure their tokens correctly...? 64 | * 65 | * @return true if BungeeGuard has not yet been configured 66 | */ 67 | public boolean isUsingDefaultConfig() { 68 | return this.allowedTokens.contains("the token generated by the proxy goes here") || 69 | this.allowedTokens.contains("you can add as many as you like."); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /bungeeguard-backend/src/main/java/me/lucko/bungeeguard/backend/listener/AbstractHandshakeListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of BungeeGuard, licensed under the MIT License. 3 | * 4 | * Copyright (c) lucko (Luck) 5 | * Copyright (c) contributors 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | package me.lucko.bungeeguard.backend.listener; 27 | 28 | import me.lucko.bungeeguard.backend.BungeeGuardBackend; 29 | import me.lucko.bungeeguard.backend.TokenStore; 30 | 31 | /** 32 | * An abstract handshake listener. 33 | */ 34 | public abstract class AbstractHandshakeListener { 35 | protected final BungeeGuardBackend plugin; 36 | protected final TokenStore tokenStore; 37 | 38 | protected final String noDataKickMessage; 39 | protected final String invalidTokenKickMessage; 40 | 41 | protected AbstractHandshakeListener(BungeeGuardBackend plugin, TokenStore tokenStore) { 42 | this.plugin = plugin; 43 | this.tokenStore = tokenStore; 44 | this.noDataKickMessage = plugin.getMessage("no-data-kick-message"); 45 | this.invalidTokenKickMessage = plugin.getMessage("invalid-token-kick-message"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /bungeeguard-bungee/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | me.lucko 9 | bungeeguard 10 | 1.4-SNAPSHOT 11 | 12 | 13 | bungeeguard-bungee 14 | 15 | 16 | 9 17 | 9 18 | 19 | 20 | 21 | 22 | 23 | ${project.basedir}/src/main/resources 24 | true 25 | 26 | *.yml 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | net.md-5 35 | bungeecord-api 36 | ${bungee.version} 37 | provided 38 | 39 | 40 | io.netty 41 | * 42 | 43 | 44 | 45 | 46 | net.md-5 47 | bungeecord-proxy 48 | ${bungee.version} 49 | provided 50 | 51 | 52 | io.netty 53 | * 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /bungeeguard-bungee/src/main/java/me/lucko/bungeeguard/bungee/BungeeGuardProxyPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of BungeeGuard, licensed under the MIT License. 3 | * 4 | * Copyright (c) lucko (Luck) 5 | * Copyright (c) contributors 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | package me.lucko.bungeeguard.bungee; 27 | 28 | import com.google.common.base.Preconditions; 29 | 30 | import net.md_5.bungee.api.event.LoginEvent; 31 | import net.md_5.bungee.api.plugin.Listener; 32 | import net.md_5.bungee.api.plugin.Plugin; 33 | import net.md_5.bungee.config.Configuration; 34 | import net.md_5.bungee.config.ConfigurationProvider; 35 | import net.md_5.bungee.config.YamlConfiguration; 36 | import net.md_5.bungee.connection.InitialHandler; 37 | import net.md_5.bungee.event.EventHandler; 38 | 39 | import java.io.File; 40 | import java.security.SecureRandom; 41 | import java.util.logging.Level; 42 | 43 | /** 44 | * BungeeCord plugin which injects a special authentication token into a players 45 | * profile properties when they connect to a backend server. 46 | */ 47 | public class BungeeGuardProxyPlugin extends Plugin implements Listener { 48 | 49 | // characters to use to build a token 50 | private static final String TOKEN_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 51 | 52 | /** 53 | * Randomly generates a new token 54 | * 55 | * @param length the length of the token 56 | * @return a new token 57 | */ 58 | private static String generateToken(int length) { 59 | Preconditions.checkArgument(length > 0); 60 | StringBuilder sb = new StringBuilder(); 61 | SecureRandom random = new SecureRandom(); 62 | for (int i = 0; i < length; i++) { 63 | sb.append(TOKEN_CHARS.charAt(random.nextInt(TOKEN_CHARS.length()))); 64 | } 65 | return sb.toString(); 66 | } 67 | 68 | /** 69 | * The auth token to inject into the property map 70 | */ 71 | private String token = null; 72 | 73 | @Override 74 | public void onEnable() { 75 | 76 | // load a token from the config, if present 77 | ConfigurationProvider provider = ConfigurationProvider.getProvider(YamlConfiguration.class); 78 | 79 | getDataFolder().mkdirs(); 80 | File file = new File(getDataFolder(), "token.yml"); 81 | 82 | if (file.exists()) { 83 | try { 84 | Configuration configuration = provider.load(file); 85 | this.token = configuration.getString("token", null); 86 | } catch (Exception e) { 87 | getLogger().log(Level.SEVERE, "Unable to load token from config", e); 88 | } 89 | } 90 | 91 | if (this.token == null || this.token.isEmpty()) { 92 | this.token = generateToken(64); 93 | 94 | Configuration configuration = new Configuration(); 95 | configuration.set("token", this.token); 96 | 97 | try { 98 | provider.save(configuration, file); 99 | } catch (Exception e) { 100 | getLogger().log(Level.SEVERE, "Unable to save token in the config", e); 101 | } 102 | } 103 | 104 | getProxy().getPluginManager().registerListener(this, this); 105 | } 106 | 107 | @EventHandler 108 | public void onLogin(LoginEvent e) { 109 | // inject our spoofed loginresult instance into the initial handler 110 | InitialHandler con = (InitialHandler) e.getConnection(); 111 | SpoofedLoginResult.inject(con, this.token); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /bungeeguard-bungee/src/main/java/me/lucko/bungeeguard/bungee/SpoofedLoginResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of BungeeGuard, licensed under the MIT License. 3 | * 4 | * Copyright (c) lucko (Luck) 5 | * Copyright (c) contributors 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | package me.lucko.bungeeguard.bungee; 27 | 28 | import net.md_5.bungee.ServerConnector; 29 | import net.md_5.bungee.connection.InitialHandler; 30 | import net.md_5.bungee.connection.LoginResult; 31 | import net.md_5.bungee.protocol.Property; 32 | 33 | import java.lang.reflect.Field; 34 | import java.util.Arrays; 35 | 36 | /** 37 | * Extension of {@link LoginResult} which returns a modified Property array when 38 | * {@link #getProperties()} is called by the ServerConnector implementation. 39 | * 40 | * To achieve this, the stack trace is analyzed. This is kinda crappy, but is the only way 41 | * to modify the properties without leaking the token to other clients via the tablist. 42 | */ 43 | class SpoofedLoginResult extends LoginResult { 44 | private static final Field PROFILE_FIELD; 45 | 46 | private static final StackWalker STACK_WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); 47 | 48 | static { 49 | try { 50 | PROFILE_FIELD = InitialHandler.class.getDeclaredField("loginProfile"); 51 | PROFILE_FIELD.setAccessible(true); 52 | } catch (ReflectiveOperationException e) { 53 | throw new ExceptionInInitializerError(e); 54 | } 55 | } 56 | 57 | static void inject(InitialHandler handler, String token) { 58 | LoginResult profile = handler.getLoginProfile(); 59 | LoginResult newProfile; 60 | 61 | // profile is null for offline mode servers 62 | if (profile == null) { 63 | newProfile = new SpoofedLoginResult(token); 64 | } else { 65 | newProfile = new SpoofedLoginResult(profile, token); 66 | } 67 | 68 | try { 69 | PROFILE_FIELD.set(handler, newProfile); 70 | } catch (IllegalAccessException e) { 71 | throw new RuntimeException(e); 72 | } 73 | } 74 | 75 | private final Property bungeeGuardToken; 76 | private final Property[] bungeeGuardTokenArray; 77 | private final boolean offline; 78 | 79 | // online mode constructor 80 | protected SpoofedLoginResult(LoginResult oldProfile, String bungeeGuardToken) { 81 | super(oldProfile.getId(), oldProfile.getName(), oldProfile.getProperties()); 82 | this.bungeeGuardToken = new Property("bungeeguard-token", bungeeGuardToken, ""); 83 | this.bungeeGuardTokenArray = new Property[]{this.bungeeGuardToken}; 84 | this.offline = false; 85 | } 86 | 87 | // offline mode constructor 88 | protected SpoofedLoginResult(String bungeeGuardToken) { 89 | super(null, null, new Property[0]); 90 | this.bungeeGuardToken = new Property("bungeeguard-token", bungeeGuardToken, ""); 91 | this.bungeeGuardTokenArray = new Property[]{this.bungeeGuardToken}; 92 | this.offline = true; 93 | } 94 | 95 | @Override 96 | public Property[] getProperties() { 97 | StackWalker.StackFrame frame = STACK_WALKER.walk(s -> 98 | // find the first frame that starts with "net.md_5.bungee" 99 | s.dropWhile(f -> !f.getClassName().startsWith("net.md_5.bungee")) 100 | .findFirst() 101 | .orElse(null) 102 | ); 103 | 104 | // if the getProperties method is being called by the server connector, include our token in the properties 105 | if (frame != null && frame.getDeclaringClass() == ServerConnector.class && frame.getMethodName().equals("connected")) { 106 | return addTokenProperty(super.getProperties()); 107 | } else { 108 | return super.getProperties(); 109 | } 110 | } 111 | 112 | private Property[] addTokenProperty(Property[] properties) { 113 | if (properties.length == 0) { 114 | return this.bungeeGuardTokenArray; 115 | } 116 | 117 | Property[] newProperties = Arrays.copyOf(properties, properties.length + 1); 118 | newProperties[properties.length] = this.bungeeGuardToken; 119 | return newProperties; 120 | } 121 | 122 | @Override 123 | public String getId() { 124 | if (this.offline) { 125 | throw new RuntimeException("getId called for offline variant of SpoofedLoginResult"); 126 | } 127 | return super.getId(); 128 | } 129 | 130 | @Override 131 | public String getName() { 132 | if (this.offline) { 133 | throw new RuntimeException("getId called for offline variant of SpoofedLoginResult"); 134 | } 135 | return super.getId(); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /bungeeguard-bungee/src/main/resources/bungee.yml: -------------------------------------------------------------------------------- 1 | name: BungeeGuard 2 | version: ${project.version} 3 | description: A plugin-based security/firewall solution for BungeeCord and Velocity proxies. 4 | author: Luck 5 | main: me.lucko.bungeeguard.bungee.BungeeGuardProxyPlugin 6 | -------------------------------------------------------------------------------- /bungeeguard-spigot-legacy/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | me.lucko 9 | bungeeguard 10 | 1.4-SNAPSHOT 11 | 12 | 13 | bungeeguard-spigot-legacy 14 | 1.4-SNAPSHOT 15 | 16 | 17 | 18 | 19 | ${project.basedir}/src/main/resources 20 | true 21 | 22 | *.yml 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | dmulloy2-repo 31 | https://repo.dmulloy2.net/repository/public/ 32 | 33 | 34 | 35 | 36 | 37 | me.lucko 38 | bungeeguard-backend 39 | ${project.version} 40 | 41 | 42 | io.papermc.paper 43 | paper-api 44 | ${paper.version} 45 | provided 46 | 47 | 48 | com.comphenix.protocol 49 | ProtocolLib 50 | 4.5.0 51 | provided 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /bungeeguard-spigot-legacy/src/main/java/me/lucko/bungeeguard/spigot/LegacyProtocolKick.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of BungeeGuard, licensed under the MIT License. 3 | * 4 | * Copyright (c) lucko (Luck) 5 | * Copyright (c) contributors 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | package me.lucko.bungeeguard.spigot; 27 | 28 | import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; 29 | import org.bukkit.entity.Player; 30 | 31 | import java.io.IOException; 32 | 33 | /** 34 | * Hacky way to kick players with the legacy Protocol (4.x series) 35 | */ 36 | public class LegacyProtocolKick { 37 | 38 | public static void kick(Player player) throws IllegalAccessException, IOException { 39 | TemporaryPlayerFactory.getInjectorFromPlayer(player).getSocket().close(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /bungeeguard-spigot/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | me.lucko 9 | bungeeguard 10 | 1.4-SNAPSHOT 11 | 12 | 13 | bungeeguard-spigot 14 | 1.4-SNAPSHOT 15 | 16 | 17 | 18 | 19 | ${project.basedir}/src/main/resources 20 | true 21 | 22 | *.yml 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | me.lucko 31 | bungeeguard-backend 32 | ${project.version} 33 | 34 | 35 | io.papermc.paper 36 | paper-api 37 | ${paper.version} 38 | provided 39 | 40 | 41 | com.comphenix.protocol 42 | ProtocolLib 43 | 5.0.0-SNAPSHOT 44 | provided 45 | 46 | 47 | me.lucko 48 | bungeeguard-spigot-legacy 49 | 1.4-SNAPSHOT 50 | compile 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /bungeeguard-spigot/src/main/java/me/lucko/bungeeguard/spigot/BungeeCordHandshake.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of BungeeGuard, licensed under the MIT License. 3 | * 4 | * Copyright (c) lucko (Luck) 5 | * Copyright (c) contributors 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | package me.lucko.bungeeguard.spigot; 27 | 28 | import com.google.gson.Gson; 29 | import com.google.gson.JsonObject; 30 | import com.google.gson.reflect.TypeToken; 31 | import me.lucko.bungeeguard.backend.TokenStore; 32 | 33 | import java.lang.reflect.Type; 34 | import java.nio.charset.StandardCharsets; 35 | import java.util.Base64; 36 | import java.util.Iterator; 37 | import java.util.LinkedList; 38 | import java.util.List; 39 | import java.util.UUID; 40 | 41 | /** 42 | * Encapsulates a BungeeCord "ip forwarding" handshake result. 43 | */ 44 | public class BungeeCordHandshake { 45 | 46 | /** The name of the BungeeGuard auth token. */ 47 | private static final String BUNGEEGUARD_TOKEN_NAME = "bungeeguard-token"; 48 | /** The key used to define the name of properties in the handshake. */ 49 | private static final String PROPERTY_NAME_KEY = "name"; 50 | /** The key used to define the value of properties in the handshake. */ 51 | private static final String PROPERTY_VALUE_KEY = "value"; 52 | /** The maximum allowed length of the handshake. */ 53 | private static final int HANDSHAKE_LENGTH_LIMIT = 2500; 54 | 55 | /** Shared Gson instance. */ 56 | private static final Gson GSON = new Gson(); 57 | /** The type of the property list in the handshake. */ 58 | private static final Type PROPERTY_LIST_TYPE = new TypeToken>(){}.getType(); 59 | 60 | /** 61 | * Decodes a BungeeCord handshake, additionally ensuring it contains a 62 | * BungeeGuard token allowed by the {@link TokenStore}. 63 | * 64 | * @param handshake the handshake data 65 | * @param tokenStore the token store 66 | * @return the handshake result 67 | */ 68 | public static BungeeCordHandshake decodeAndVerify(String handshake, TokenStore tokenStore) { 69 | try { 70 | return decodeAndVerify0(handshake, tokenStore); 71 | } catch (Exception e) { 72 | new Exception("Failed to decode handshake", e).printStackTrace(); 73 | return new Fail(Fail.Reason.INVALID_HANDSHAKE, encodeBase64(handshake)); 74 | } 75 | } 76 | 77 | private static BungeeCordHandshake decodeAndVerify0(String handshake, TokenStore tokenStore) throws Exception { 78 | if (tokenStore.isUsingDefaultConfig()) { 79 | return new Fail(Fail.Reason.INCORRECT_TOKEN, "Allowed tokens have not been configured! Please refer to https://github.com/lucko/BungeeGuard/blob/master/INSTALLATION.md for help."); 80 | } 81 | 82 | if (handshake.length() > HANDSHAKE_LENGTH_LIMIT) { 83 | return new Fail(Fail.Reason.INVALID_HANDSHAKE, "handshake length " + handshake.length() + " is > " + HANDSHAKE_LENGTH_LIMIT); 84 | } 85 | 86 | String[] split = handshake.split("\00"); 87 | if (split.length != 3 && split.length != 4) { 88 | return new Fail(Fail.Reason.INVALID_HANDSHAKE, encodeBase64(handshake)); 89 | } 90 | 91 | String serverHostname = split[0]; 92 | String socketAddressHostname = split[1]; 93 | UUID uniqueId = UUID.fromString(split[2].replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5")); 94 | 95 | String connectionDescription = uniqueId + " @ " + encodeBase64(socketAddressHostname); 96 | 97 | if (split.length == 3) { 98 | return new Fail(Fail.Reason.NO_TOKEN, connectionDescription); 99 | } 100 | 101 | List properties = new LinkedList<>(GSON.fromJson(split[3], PROPERTY_LIST_TYPE)); 102 | if (properties.isEmpty()) { 103 | return new Fail(Fail.Reason.NO_TOKEN, connectionDescription); 104 | } 105 | 106 | String bungeeGuardToken = null; 107 | for (Iterator iterator = properties.iterator(); iterator.hasNext(); ) { 108 | JsonObject property = iterator.next(); 109 | if (property.get(PROPERTY_NAME_KEY).getAsString().equals(BUNGEEGUARD_TOKEN_NAME)) { 110 | if (bungeeGuardToken != null) { 111 | return new Fail(Fail.Reason.INCORRECT_TOKEN, connectionDescription + " - more than one token"); 112 | } 113 | 114 | bungeeGuardToken = property.get(PROPERTY_VALUE_KEY).getAsString(); 115 | iterator.remove(); 116 | } 117 | } 118 | 119 | if (bungeeGuardToken == null) { 120 | return new Fail(Fail.Reason.NO_TOKEN, connectionDescription); 121 | } 122 | 123 | if (!tokenStore.isAllowed(bungeeGuardToken)) { 124 | return new Fail(Fail.Reason.INCORRECT_TOKEN, connectionDescription + " - " + encodeBase64(bungeeGuardToken)); 125 | } 126 | 127 | String newPropertiesString = GSON.toJson(properties, PROPERTY_LIST_TYPE); 128 | return new Success(serverHostname, socketAddressHostname, uniqueId, newPropertiesString); 129 | } 130 | 131 | public static String encodeBase64(String s) { 132 | return Base64.getEncoder().encodeToString(s.getBytes(StandardCharsets.UTF_8)); 133 | } 134 | 135 | /** 136 | * Encapsulates a successful handshake. 137 | */ 138 | public static final class Success extends BungeeCordHandshake { 139 | private final String serverHostname; 140 | private final String socketAddressHostname; 141 | private final UUID uniqueId; 142 | private final String propertiesJson; 143 | 144 | Success(String serverHostname, String socketAddressHostname, UUID uniqueId, String propertiesJson) { 145 | this.serverHostname = serverHostname; 146 | this.socketAddressHostname = socketAddressHostname; 147 | this.uniqueId = uniqueId; 148 | this.propertiesJson = propertiesJson; 149 | } 150 | 151 | public String serverHostname() { 152 | return this.serverHostname; 153 | } 154 | 155 | public String socketAddressHostname() { 156 | return this.socketAddressHostname; 157 | } 158 | 159 | public UUID uniqueId() { 160 | return this.uniqueId; 161 | } 162 | 163 | public String propertiesJson() { 164 | return this.propertiesJson; 165 | } 166 | 167 | /** 168 | * Re-encodes this handshake to the format used by BungeeCord. 169 | * 170 | * @return an encoded string for the handshake 171 | */ 172 | public String encode() { 173 | return this.serverHostname + "\00" + this.socketAddressHostname + "\00" + this.uniqueId + "\00" + this.propertiesJson; 174 | } 175 | } 176 | 177 | /** 178 | * Encapsulates an unsuccessful handshake. 179 | */ 180 | public static final class Fail extends BungeeCordHandshake { 181 | private final Reason reason; 182 | private final String connectionDescription; 183 | 184 | Fail(Reason reason, String connectionDescription) { 185 | this.reason = reason; 186 | this.connectionDescription = connectionDescription; 187 | } 188 | 189 | public Reason reason() { 190 | return this.reason; 191 | } 192 | 193 | public String describeConnection() { 194 | return this.connectionDescription; 195 | } 196 | 197 | public enum Reason { 198 | INVALID_HANDSHAKE, NO_TOKEN, INCORRECT_TOKEN 199 | } 200 | } 201 | 202 | } 203 | -------------------------------------------------------------------------------- /bungeeguard-spigot/src/main/java/me/lucko/bungeeguard/spigot/BungeeGuardBackendPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of BungeeGuard, licensed under the MIT License. 3 | * 4 | * Copyright (c) lucko (Luck) 5 | * Copyright (c) contributors 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | package me.lucko.bungeeguard.spigot; 27 | 28 | import me.lucko.bungeeguard.backend.BungeeGuardBackend; 29 | import me.lucko.bungeeguard.backend.TokenStore; 30 | import me.lucko.bungeeguard.spigot.listener.PaperHandshakeListener; 31 | import me.lucko.bungeeguard.spigot.listener.ProtocolHandshakeListener; 32 | 33 | import org.bukkit.ChatColor; 34 | import org.bukkit.command.Command; 35 | import org.bukkit.command.CommandSender; 36 | import org.bukkit.command.ConsoleCommandSender; 37 | import org.bukkit.plugin.java.JavaPlugin; 38 | 39 | import java.util.List; 40 | 41 | /** 42 | * Simple plugin which overrides the BungeeCord handshake protocol, and cancels all 43 | * connections which don't contain a special auth token set by the proxy. 44 | * 45 | * The token is included within the player's profile properties, but removed during the handshake. 46 | */ 47 | public class BungeeGuardBackendPlugin extends JavaPlugin implements BungeeGuardBackend { 48 | 49 | private TokenStore tokenStore; 50 | 51 | @Override 52 | public void onEnable() { 53 | saveDefaultConfig(); 54 | this.tokenStore = new TokenStore(this); 55 | this.tokenStore.load(); 56 | 57 | if (!getServer().spigot().getConfig().getBoolean("settings.bungeecord", false)) { 58 | getLogger().severe("------------------------------------------------------------"); 59 | getLogger().severe("'settings.bungeecord' is set to false in spigot.yml."); 60 | getLogger().severe(""); 61 | getLogger().severe("BungeeGuard cannot function unless this property is set to true."); 62 | getLogger().severe("The server will now shutdown as a precaution."); 63 | getLogger().severe("------------------------------------------------------------"); 64 | getServer().shutdown(); 65 | return; 66 | } 67 | 68 | if (isPaperHandshakeEvent()) { 69 | getLogger().info("Using Paper's PlayerHandshakeEvent to listen for connections."); 70 | 71 | PaperHandshakeListener listener = new PaperHandshakeListener(this, this.tokenStore); 72 | getServer().getPluginManager().registerEvents(listener, this); 73 | 74 | } else if (hasProtocolLib()) { 75 | getLogger().info("Using ProtocolLib to listen for connections."); 76 | 77 | ProtocolHandshakeListener listener = new ProtocolHandshakeListener(this, this.tokenStore); 78 | listener.registerAdapter(this); 79 | 80 | } else { 81 | getLogger().severe("------------------------------------------------------------"); 82 | getLogger().severe("BungeeGuard is unable to listen for handshakes! The server will now shut down."); 83 | getLogger().severe(""); 84 | if (isPaperServer()) { 85 | getLogger().severe("Please install ProtocolLib in order to use this plugin."); 86 | } else { 87 | getLogger().severe("If your server is using 1.9.4 or newer, please upgrade to Paper - https://papermc.io"); 88 | getLogger().severe("If your server is using 1.8.8 or older, please install ProtocolLib."); 89 | } 90 | getLogger().severe("------------------------------------------------------------"); 91 | getServer().shutdown(); 92 | } 93 | } 94 | 95 | @Override 96 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 97 | if (!(sender instanceof ConsoleCommandSender)) { 98 | sender.sendMessage(ChatColor.RED + "Sorry, this command can only be ran from the console."); 99 | return true; 100 | } 101 | 102 | if (args.length == 0 || !args[0].equalsIgnoreCase("reload")) { 103 | sender.sendMessage(ChatColor.RED + "Running BungeeGuard v" + getDescription().getVersion()); 104 | sender.sendMessage(ChatColor.GRAY + "Use '/bungeeguard reload' to reload the configuration."); 105 | return true; 106 | } 107 | 108 | this.tokenStore.reload(); 109 | sender.sendMessage(ChatColor.RED + "BungeeGuard configuration reloaded."); 110 | return true; 111 | } 112 | 113 | @Override 114 | public String getMessage(String key) { 115 | return ChatColor.translateAlternateColorCodes('&', getConfig().getString(key)); 116 | } 117 | 118 | @Override 119 | public List getTokens() { 120 | return getConfig().getStringList("allowed-tokens"); 121 | } 122 | 123 | private static boolean isPaperHandshakeEvent() { 124 | return classExists("com.destroystokyo.paper.event.player.PlayerHandshakeEvent"); 125 | } 126 | 127 | private static boolean isPaperServer() { 128 | return classExists("com.destroystokyo.paper.PaperConfig"); 129 | } 130 | 131 | private boolean hasProtocolLib() { 132 | return getServer().getPluginManager().getPlugin("ProtocolLib") != null; 133 | } 134 | 135 | private static boolean classExists(String className) { 136 | try { 137 | Class.forName(className); 138 | return true; 139 | } catch (ClassNotFoundException e) { 140 | return false; 141 | } 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /bungeeguard-spigot/src/main/java/me/lucko/bungeeguard/spigot/listener/PaperHandshakeListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of BungeeGuard, licensed under the MIT License. 3 | * 4 | * Copyright (c) lucko (Luck) 5 | * Copyright (c) contributors 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | package me.lucko.bungeeguard.spigot.listener; 27 | 28 | import com.destroystokyo.paper.event.player.PlayerHandshakeEvent; 29 | 30 | import me.lucko.bungeeguard.backend.TokenStore; 31 | import me.lucko.bungeeguard.backend.listener.AbstractHandshakeListener; 32 | import me.lucko.bungeeguard.spigot.BungeeCordHandshake; 33 | 34 | import me.lucko.bungeeguard.spigot.BungeeGuardBackendPlugin; 35 | import org.bukkit.event.EventHandler; 36 | import org.bukkit.event.EventPriority; 37 | import org.bukkit.event.Listener; 38 | 39 | import java.lang.reflect.Method; 40 | import java.util.logging.Level; 41 | import java.util.logging.Logger; 42 | 43 | /** 44 | * A handshake listener using Paper's {@link PlayerHandshakeEvent}. 45 | */ 46 | public class PaperHandshakeListener extends AbstractHandshakeListener implements Listener { 47 | 48 | private static final Method getOriginalSocketAddressHostname; 49 | static { 50 | Method method = null; 51 | try { 52 | method = PlayerHandshakeEvent.class.getMethod("getOriginalSocketAddressHostname"); 53 | } catch (NoSuchMethodException ignored) { 54 | // Paper added this method in 1.16 55 | } 56 | getOriginalSocketAddressHostname = method; 57 | } 58 | 59 | private final Logger logger; 60 | 61 | public PaperHandshakeListener(BungeeGuardBackendPlugin plugin, TokenStore tokenStore) { 62 | super(plugin, tokenStore); 63 | this.logger = plugin.getLogger(); 64 | } 65 | 66 | @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) 67 | public void onHandshake(PlayerHandshakeEvent e) { 68 | BungeeCordHandshake decoded = BungeeCordHandshake.decodeAndVerify(e.getOriginalHandshake(), this.tokenStore); 69 | 70 | if (decoded instanceof BungeeCordHandshake.Fail) { 71 | BungeeCordHandshake.Fail fail = (BungeeCordHandshake.Fail) decoded; 72 | String ip = ""; 73 | if (getOriginalSocketAddressHostname != null) { 74 | try { 75 | ip = getOriginalSocketAddressHostname.invoke(e) + " - "; 76 | } catch (ReflectiveOperationException ex) { 77 | this.logger.log(Level.SEVERE, "Unable to get original address", ex); 78 | } 79 | } 80 | 81 | this.logger.warning("Denying connection from " + ip + fail.describeConnection() + " - reason: " + fail.reason().name()); 82 | 83 | if (fail.reason() == BungeeCordHandshake.Fail.Reason.INVALID_HANDSHAKE) { 84 | e.setFailMessage(this.noDataKickMessage); 85 | } else { 86 | e.setFailMessage(this.invalidTokenKickMessage); 87 | } 88 | 89 | e.setFailed(true); 90 | return; 91 | } 92 | 93 | BungeeCordHandshake.Success data = (BungeeCordHandshake.Success) decoded; 94 | e.setServerHostname(data.serverHostname()); 95 | e.setSocketAddressHostname(data.socketAddressHostname()); 96 | e.setUniqueId(data.uniqueId()); 97 | e.setPropertiesJson(data.propertiesJson()); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /bungeeguard-spigot/src/main/java/me/lucko/bungeeguard/spigot/listener/ProtocolHandshakeListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of BungeeGuard, licensed under the MIT License. 3 | * 4 | * Copyright (c) lucko (Luck) 5 | * Copyright (c) contributors 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | package me.lucko.bungeeguard.spigot.listener; 27 | 28 | import com.comphenix.protocol.PacketType; 29 | import com.comphenix.protocol.ProtocolLibrary; 30 | import com.comphenix.protocol.events.ListenerPriority; 31 | import com.comphenix.protocol.events.PacketAdapter; 32 | import com.comphenix.protocol.events.PacketContainer; 33 | import com.comphenix.protocol.events.PacketEvent; 34 | import com.comphenix.protocol.injector.temporary.MinimalInjector; 35 | import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory; 36 | import com.comphenix.protocol.wrappers.WrappedChatComponent; 37 | 38 | import me.lucko.bungeeguard.backend.BungeeGuardBackend; 39 | import me.lucko.bungeeguard.backend.TokenStore; 40 | import me.lucko.bungeeguard.backend.listener.AbstractHandshakeListener; 41 | import me.lucko.bungeeguard.spigot.BungeeCordHandshake; 42 | import me.lucko.bungeeguard.spigot.LegacyProtocolKick; 43 | 44 | import net.md_5.bungee.api.chat.TextComponent; 45 | import net.md_5.bungee.chat.ComponentSerializer; 46 | 47 | import org.bukkit.entity.Player; 48 | import org.bukkit.plugin.Plugin; 49 | 50 | import java.net.InetSocketAddress; 51 | import java.util.logging.Level; 52 | 53 | /** 54 | * A handshake listener using ProtocolLib. 55 | */ 56 | public class ProtocolHandshakeListener extends AbstractHandshakeListener { 57 | static boolean isLegacyProtocolLib = false; // Before 5.x series. 58 | 59 | public ProtocolHandshakeListener(BungeeGuardBackend plugin, TokenStore tokenStore) { 60 | super(plugin, tokenStore); 61 | } 62 | 63 | public void registerAdapter(Plugin plugin) { 64 | ProtocolLibrary.getProtocolManager().addPacketListener(new Adapter(plugin)); 65 | } 66 | 67 | private final class Adapter extends PacketAdapter { 68 | 69 | Adapter(Plugin plugin) { 70 | super(plugin, ListenerPriority.LOWEST, PacketType.Handshake.Client.SET_PROTOCOL); 71 | try { 72 | Class.forName("com.comphenix.protocol.injector.temporary.MinimalInjector"); 73 | plugin.getLogger().info("Using modern (v5) ProtocolLib adapter."); 74 | } catch (ClassNotFoundException e) { 75 | plugin.getLogger().info("Using legacy (v4) ProtocolLib adapter."); 76 | isLegacyProtocolLib = true; 77 | } 78 | } 79 | 80 | @Override 81 | public void onPacketReceiving(PacketEvent event) { 82 | PacketContainer packet = event.getPacket(); 83 | 84 | // only handle the LOGIN phase 85 | PacketType.Protocol state = packet.getProtocols().read(0); 86 | if (state != PacketType.Protocol.LOGIN) { 87 | return; 88 | } 89 | 90 | String handshake = packet.getStrings().read(0); 91 | BungeeCordHandshake decoded = BungeeCordHandshake.decodeAndVerify(handshake, ProtocolHandshakeListener.this.tokenStore); 92 | 93 | if (decoded instanceof BungeeCordHandshake.Fail) { 94 | String ip = "null"; 95 | Player player = event.getPlayer(); 96 | InetSocketAddress address = player.getAddress(); 97 | if (address != null) { 98 | ip = address.getHostString(); 99 | if (ip.length() > 15) { 100 | ip = BungeeCordHandshake.encodeBase64(ip); 101 | } 102 | } 103 | BungeeCordHandshake.Fail fail = (BungeeCordHandshake.Fail) decoded; 104 | this.plugin.getLogger().warning("Denying connection from " + ip + " - " + fail.describeConnection() + " - reason: " + fail.reason().name()); 105 | 106 | String kickMessage; 107 | if (fail.reason() == BungeeCordHandshake.Fail.Reason.INVALID_HANDSHAKE) { 108 | kickMessage = ProtocolHandshakeListener.this.noDataKickMessage; 109 | } else { 110 | kickMessage = ProtocolHandshakeListener.this.invalidTokenKickMessage; 111 | } 112 | 113 | try { 114 | closeConnection(player, kickMessage); 115 | } catch (Exception e) { 116 | this.plugin.getLogger().log(Level.SEVERE, "An error occurred while closing connection for " + player, e); 117 | } 118 | 119 | // just in-case the connection didn't close, screw up the hostname 120 | // so Spigot can't pick up anything that might've been spoofed in nms.HandshakeListener 121 | packet.getStrings().write(0, "null"); 122 | 123 | return; 124 | } 125 | 126 | // great, handshake was decoded and verified successfully. 127 | // we can re-encode the handshake now so Spigot can pick up the spoofed stuff. 128 | BungeeCordHandshake.Success data = (BungeeCordHandshake.Success) decoded; 129 | packet.getStrings().write(0, data.encode()); 130 | } 131 | } 132 | 133 | private static void closeConnection(Player player, String kickMessage) throws Exception { 134 | WrappedChatComponent component = WrappedChatComponent.fromJson(ComponentSerializer.toString(TextComponent.fromLegacyText(kickMessage))); 135 | 136 | PacketContainer packet = new PacketContainer(PacketType.Login.Server.DISCONNECT); 137 | packet.getModifier().writeDefaults(); 138 | packet.getChatComponents().write(0, component); 139 | 140 | // send custom disconnect message to client 141 | ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet); 142 | 143 | if (isLegacyProtocolLib) { 144 | LegacyProtocolKick.kick(player); 145 | } else { 146 | // call PlayerConnection#disconnect to ensure the underlying socket is closed 147 | MinimalInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player); 148 | injector.disconnect(""); 149 | } 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /bungeeguard-spigot/src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | # BungeeGuard Configuration 2 | 3 | # Allowed authentication tokens. 4 | allowed-tokens: 5 | - "the token generated by the proxy goes here" 6 | - "you can add as many as you like." 7 | 8 | 9 | # Messages 10 | 11 | # Kick message sent to connections without any forwarded data from the proxy. 12 | # Most likely a vanilla client connecting directly to the server, bypassing the proxy. 13 | no-data-kick-message: "&cUnable to authenticate - no data was forwarded by the proxy." 14 | 15 | # Kick message sent to connections with forwarding data, but without a correct BungeeGuard token 16 | # included in their handshake. Assuming BungeeGuard is installed correctly on all proxies, 17 | # this is most likely a client trying to exploit the BungeeCord protocol to spoof their uuid. 18 | invalid-token-kick-message: "&cUnable to authenticate." 19 | -------------------------------------------------------------------------------- /bungeeguard-spigot/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: BungeeGuard 2 | version: ${project.version} 3 | description: A plugin-based security/firewall solution for BungeeCord and Velocity proxies. 4 | author: Luck 5 | load: STARTUP 6 | 7 | main: me.lucko.bungeeguard.spigot.BungeeGuardBackendPlugin 8 | softdepend: [ProtocolLib] 9 | api-version: 1.13 10 | folia-supported: true 11 | 12 | commands: 13 | bungeeguard: 14 | description: Reloads the configuration 15 | permission: bungeeguard.reload 16 | usage: /bungeeguard reload -------------------------------------------------------------------------------- /bungeeguard-sponge/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | me.lucko 9 | bungeeguard 10 | 1.4-SNAPSHOT 11 | 12 | 13 | bungeeguard-sponge 14 | 15 | 16 | BungeeGuard-Sponge 17 | 18 | 19 | ../ 20 | false 21 | 22 | LICENSE.txt 23 | 24 | 25 | 26 | ${project.basedir}/src/main/resources 27 | true 28 | 29 | *.conf 30 | 31 | 32 | 33 | 34 | 35 | org.apache.maven.plugins 36 | maven-shade-plugin 37 | 3.2.3 38 | 39 | 40 | package 41 | 42 | shade 43 | 44 | 45 | 46 | 47 | 48 | org.codehaus.mojo 49 | templating-maven-plugin 50 | 1.0.0 51 | 52 | 53 | filter-src 54 | 55 | filter-sources 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | sponge-repo 66 | https://repo.spongepowered.org/repository/maven-public/ 67 | 68 | 69 | 70 | 71 | 72 | me.lucko 73 | bungeeguard-backend 74 | ${project.version} 75 | 76 | 77 | org.spongepowered 78 | spongeapi 79 | 7.3.0 80 | provided 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /bungeeguard-sponge/src/main/java-templates/me/lucko/bungeeguard/sponge/BungeeGuardVersion.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of BungeeGuard, licensed under the MIT License. 3 | * 4 | * Copyright (c) lucko (Luck) 5 | * Copyright (c) contributors 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | package me.lucko.bungeeguard.sponge; 27 | 28 | public final class BungeeGuardVersion { 29 | 30 | public static final String VERSION = "${project.version}"; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /bungeeguard-sponge/src/main/java/me/lucko/bungeeguard/sponge/BungeeGuardSponge.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of BungeeGuard, licensed under the MIT License. 3 | * 4 | * Copyright (c) lucko (Luck) 5 | * Copyright (c) contributors 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | package me.lucko.bungeeguard.sponge; 27 | 28 | import com.google.common.reflect.TypeToken; 29 | import com.google.inject.Inject; 30 | 31 | import me.lucko.bungeeguard.backend.BungeeGuardBackend; 32 | import me.lucko.bungeeguard.backend.TokenStore; 33 | 34 | import ninja.leaping.configurate.ConfigurationNode; 35 | import ninja.leaping.configurate.hocon.HoconConfigurationLoader; 36 | import ninja.leaping.configurate.objectmapping.ObjectMappingException; 37 | 38 | import org.slf4j.Logger; 39 | import org.spongepowered.api.Sponge; 40 | import org.spongepowered.api.command.CommandResult; 41 | import org.spongepowered.api.command.CommandSource; 42 | import org.spongepowered.api.command.args.CommandContext; 43 | import org.spongepowered.api.command.args.GenericArguments; 44 | import org.spongepowered.api.command.source.ConsoleSource; 45 | import org.spongepowered.api.command.spec.CommandExecutor; 46 | import org.spongepowered.api.command.spec.CommandSpec; 47 | import org.spongepowered.api.config.DefaultConfig; 48 | import org.spongepowered.api.event.Listener; 49 | import org.spongepowered.api.event.game.state.GamePreInitializationEvent; 50 | import org.spongepowered.api.plugin.Plugin; 51 | import org.spongepowered.api.text.Text; 52 | import org.spongepowered.api.text.format.TextColors; 53 | 54 | import java.io.IOException; 55 | import java.io.InputStream; 56 | import java.nio.file.Files; 57 | import java.nio.file.Path; 58 | import java.util.Collections; 59 | import java.util.List; 60 | 61 | @Plugin( 62 | id = "bungeeguard", 63 | name = "BungeeGuard", 64 | version = BungeeGuardVersion.VERSION, 65 | description = "Plugin which adds a security token to the BungeeCord handshaking protocol", 66 | authors = "Luck" 67 | ) 68 | public final class BungeeGuardSponge implements BungeeGuardBackend, CommandExecutor { 69 | 70 | private final Logger logger; 71 | private final Path configPath; 72 | private final TokenStore tokenStore; 73 | 74 | private ConfigurationNode config; 75 | 76 | @Inject 77 | public BungeeGuardSponge(Logger logger, @DefaultConfig(sharedRoot = true) Path configPath) { 78 | this.logger = logger; 79 | this.configPath = configPath; 80 | this.tokenStore = new TokenStore(this); 81 | } 82 | 83 | @Listener 84 | public void onInitialization(GamePreInitializationEvent event) { 85 | if (!Files.exists(this.configPath)) { 86 | try (InputStream in = getClass().getClassLoader().getResourceAsStream("bungeeguard.conf")) { 87 | Files.copy(in, this.configPath); 88 | } catch (IOException e) { 89 | throw new RuntimeException("Unable to save default config", e); 90 | } 91 | } 92 | 93 | this.reloadConfig(); 94 | 95 | this.tokenStore.load(); 96 | 97 | CommandSpec command = CommandSpec.builder() 98 | .description(Text.of("Reloads the configuration")) 99 | .permission("bungeeguard.reload") 100 | .arguments(GenericArguments.optional( 101 | GenericArguments.literal(Text.of("reload"), "reload") 102 | )) 103 | .executor(this) 104 | .build(); 105 | 106 | Sponge.getCommandManager().register(this, command, "bungeeguard"); 107 | Sponge.getEventManager().registerListeners(this, new HandshakeListener(this, this.tokenStore, this.logger)); 108 | } 109 | 110 | @Override 111 | public CommandResult execute(CommandSource src, CommandContext args) { 112 | if (!(src instanceof ConsoleSource)) { 113 | src.sendMessage(Text.of(TextColors.RED, "Sorry, this command can only be ran from the console.")); 114 | return CommandResult.empty(); 115 | } 116 | 117 | if (!args.hasAny(Text.of("reload"))) { 118 | src.sendMessage(Text.of(TextColors.RED, "Running BungeeGuard v" + BungeeGuardVersion.VERSION)); 119 | src.sendMessage(Text.of(TextColors.GRAY, "Use '/bungeeguard reload' to reload the configuration.")); 120 | 121 | return CommandResult.empty(); 122 | } 123 | 124 | try { 125 | this.tokenStore.reload(); 126 | } catch (Exception e) { 127 | this.logger.error("An error occurred while reloading tokens", e); 128 | 129 | src.sendMessage(Text.of(TextColors.RED, "An error occurred while reloading tokens.")); 130 | 131 | return CommandResult.empty(); 132 | } 133 | 134 | src.sendMessage(Text.of(TextColors.RED, "BungeeGuard configuration reloaded.")); 135 | 136 | return CommandResult.success(); 137 | } 138 | 139 | @Override 140 | public String getMessage(String key) { 141 | return this.config.getNode(key).getString(); 142 | } 143 | 144 | @Override 145 | public List getTokens() { 146 | try { 147 | return this.config.getNode("allowed-tokens").getList(TypeToken.of(String.class)); 148 | } catch (ObjectMappingException e) { 149 | this.logger.error("Unable to load tokens", e); 150 | return Collections.emptyList(); 151 | } 152 | } 153 | 154 | @Override 155 | public void reloadConfig() { 156 | try { 157 | HoconConfigurationLoader loader = HoconConfigurationLoader.builder().setPath(this.configPath).build(); 158 | this.config = loader.load(); 159 | } catch (IOException e) { 160 | throw new RuntimeException("Unable to load config", e); 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /bungeeguard-sponge/src/main/java/me/lucko/bungeeguard/sponge/HandshakeListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of BungeeGuard, licensed under the MIT License. 3 | * 4 | * Copyright (c) lucko (Luck) 5 | * Copyright (c) contributors 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | package me.lucko.bungeeguard.sponge; 27 | 28 | import me.lucko.bungeeguard.backend.BungeeGuardBackend; 29 | import me.lucko.bungeeguard.backend.TokenStore; 30 | import me.lucko.bungeeguard.backend.listener.AbstractHandshakeListener; 31 | 32 | import org.slf4j.Logger; 33 | import org.spongepowered.api.event.Listener; 34 | import org.spongepowered.api.event.network.ClientConnectionEvent; 35 | import org.spongepowered.api.profile.GameProfile; 36 | import org.spongepowered.api.profile.property.ProfileProperty; 37 | import org.spongepowered.api.text.Text; 38 | import org.spongepowered.api.text.serializer.TextSerializers; 39 | 40 | import java.util.Collection; 41 | import java.util.Iterator; 42 | 43 | public class HandshakeListener extends AbstractHandshakeListener { 44 | 45 | private final Text noDataKickText; 46 | private final Text invalidTokenKickText; 47 | private final Logger logger; 48 | 49 | public HandshakeListener(BungeeGuardBackend plugin, TokenStore tokenStore, Logger logger) { 50 | super(plugin, tokenStore); 51 | this.logger = logger; 52 | this.noDataKickText = TextSerializers.FORMATTING_CODE.deserialize(this.noDataKickMessage); 53 | this.invalidTokenKickText = TextSerializers.FORMATTING_CODE.deserialize(this.invalidTokenKickMessage); 54 | } 55 | 56 | @Listener 57 | public void onClientAuth(ClientConnectionEvent.Auth e) { 58 | GameProfile profile = e.getProfile(); 59 | Collection tokens = profile.getPropertyMap().get("bungeeguard-token"); 60 | 61 | String bungeeGuardToken = null; 62 | 63 | for (Iterator iterator = tokens.iterator(); iterator.hasNext(); ) { 64 | bungeeGuardToken = iterator.next().getValue(); 65 | iterator.remove(); 66 | } 67 | 68 | if (bungeeGuardToken == null || !this.tokenStore.isAllowed(bungeeGuardToken)) { 69 | String connectionDescription = profile.getUniqueId() + " @ " + e.getConnection().getAddress().getHostString(); 70 | String reason = bungeeGuardToken == null ? "No Token" : "Invalid token"; 71 | 72 | this.logger.warn("Denying connection from " + connectionDescription + " - reason: " + reason); 73 | 74 | e.setMessage(bungeeGuardToken == null ? this.noDataKickText : this.invalidTokenKickText); 75 | e.setCancelled(true); 76 | e.setMessageCancelled(false); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /bungeeguard-sponge/src/main/resources/bungeeguard.conf: -------------------------------------------------------------------------------- 1 | # BungeeGuard Configuration 2 | 3 | # Allowed authentication tokens. 4 | allowed-tokens = [ 5 | "the token generated by the proxy goes here", 6 | "you can add as many as you like.", 7 | ] 8 | 9 | # Messages 10 | 11 | # Kick message sent to connections without any forwarded data from the proxy. 12 | # Most likely a vanilla client connecting directly to the server, bypassing the proxy. 13 | no-data-kick-message = "&cUnable to authenticate - no data was forwarded by the proxy." 14 | 15 | # Kick message sent to connections with forwarding data, but without a correct BungeeGuard token 16 | # included in their handshake. Assuming BungeeGuard is installed correctly on all proxies, 17 | # this is most likely a client trying to exploit the BungeeCord protocol to spoof their uuid. 18 | invalid-token-kick-message = "&cUnable to authenticate." 19 | -------------------------------------------------------------------------------- /bungeeguard-universal/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | me.lucko 9 | bungeeguard 10 | 1.4-SNAPSHOT 11 | 12 | 13 | bungeeguard-universal 14 | 1.4-SNAPSHOT 15 | 16 | 17 | clean package 18 | BungeeGuard 19 | 20 | 21 | ../ 22 | false 23 | 24 | LICENSE.txt 25 | 26 | 27 | 28 | 29 | 30 | org.apache.maven.plugins 31 | maven-shade-plugin 32 | 3.2.3 33 | 34 | 35 | package 36 | 37 | shade 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | me.lucko 48 | bungeeguard-bungee 49 | ${project.version} 50 | compile 51 | 52 | 53 | me.lucko 54 | bungeeguard-spigot 55 | ${project.version} 56 | compile 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | me.lucko 8 | bungeeguard 9 | 1.4-SNAPSHOT 10 | pom 11 | 12 | 13 | bungeeguard-bungee 14 | bungeeguard-backend 15 | bungeeguard-spigot 16 | bungeeguard-spigot-legacy 17 | bungeeguard-sponge 18 | bungeeguard-universal 19 | 20 | 21 | BungeeGuard 22 | 23 | 24 | UTF-8 25 | 1.8 26 | 1.8 27 | 28 | 1.19-R0.1-SNAPSHOT 29 | 1.19-R0.1-SNAPSHOT 30 | 31 | 32 | 33 | 34 | sonatype-snapshots-repo 35 | https://oss.sonatype.org/content/repositories/snapshots/ 36 | 37 | 38 | spigot-repo 39 | https://hub.spigotmc.org/nexus/content/groups/public/ 40 | 41 | 42 | paper-repo 43 | https://repo.papermc.io/repository/maven-public/ 44 | 45 | 46 | luck-repo 47 | https://repo.lucko.me/ 48 | 49 | 50 | 51 | --------------------------------------------------------------------------------