Add ip parameter to verify a player doesn't use an authentication proxy.

This doesn't prevent proxy connections in general, but it verifies that
the same IP that is used for connecting to the Minecraft server is also
used for authenticating against the Mojang servers.

This happens if someone uses McLeaks. They use an authentication proxy
in order to hide and control the credentials behind those leaked or
donated accounts. So a user of that service joins the server using
a direct connection, but asks the McLeaks servers to send a relevant
request to the Mojang session-servers in order to pass the premium
verification process.
This commit is contained in:
games647
2017-08-25 13:20:55 +02:00
parent 4ea7968366
commit 484855724b
24 changed files with 61 additions and 54 deletions

View File

@ -5,7 +5,7 @@
* Remove bungee chatcolor for Bukkit to support KCauldron
* Minor cleanup using inspections + Https
* Increase hook delay to let ProtocolLib inject the listener
* Drop support for old authme API + Add support for new authme API
* Drop support for old AuthMe API + Add support for new AuthMe API
* Remove ebean util usage to make it compatible with 1.12
* Do not try to hook into a plugin if auth plugin hook is already set using the FastLogin API
* Automatically register accounts if they are not in the auth plugin database but in the FastLogin database
@ -14,7 +14,7 @@
* Finally set a value to the API column
* No duplicate session login
* Fix timestamp parsing in newer versions of SQLite
* Fix Spigot console command invocation sends result to ingame players
* Fix Spigot console command invocation sends result to in game players
### 1.9
@ -170,7 +170,7 @@
### 0.5
* Added unpremium command
* Added cracked command
* Added autologin - See config
* Added config
* Added isRegistered API method

View File

@ -104,8 +104,8 @@ Put your stats id from the BungeeCord config into this file
#### How does minecraft logins work?
###### Online Mode
1. Client -> Server: I want to login, here is my username
2. Server -> Client: Okay. I'm in online mode so here is my public key for encryption and my serverid
3. Client -> Mojang: I'm player "xyz". I want to join a server with that serverid
2. Server -> Client: Okay. I'm in online mode so here is my public key for encryption and my server id
3. Client -> Mojang: I'm player "xyz". I want to join a server with that server id
4. Mojang -> Client: Session data checked. You can continue
5. Client -> Server: I received a successful response from Mojang. Here our shared secret key
6. Server -> Mojang: Does the player "xyz" with this shared secret key has a valid account to join me?
@ -155,7 +155,7 @@ of a cracked player that has the same username. The player have to proof first t
to a paid account but if we request a online mode login from a cracked player (who uses a username from
a paid account), the player will disconnect with the reason "bad login" or "Invalid session". There is no way to change
that message on the server side (without client modifications), because it's a connection between the Client and the
sessionserver.
session-server.
3. If a premium player would skip registration too, a player of a cracked account could later still register the
account and would claim and steal the account from the premium player. Because commands cannot be invoked unless the
player has a account or is logged in, protects this method also premium players

View File

@ -34,7 +34,7 @@
<url>http://repo.dmulloy2.net/content/groups/public/</url>
</repository>
<!--Authme Reloaded-->
<!--AuthMe Reloaded-->
<repository>
<id>xephi-repo</id>
<url>https://ci.xephi.fr/plugin/repository/everything/</url>
@ -52,7 +52,7 @@
<url>https://jitpack.io</url>
</repository>
<!--PlaceholerAPI -->
<!--PlaceholderAPI -->
<repository>
<id>placeholderapi</id>
<url>http://repo.extendedclip.com/content/repositories/placeholderapi/</url>

View File

@ -44,9 +44,9 @@ public class EncryptionUtil {
, serverId.getBytes(Charsets.ISO_8859_1), secretKey.getEncoded(), publicKey.getEncoded());
}
private static byte[] digestOperation(String algo, byte[]... content) {
private static byte[] digestOperation(String algorithm, byte[]... content) {
try {
MessageDigest messagedigest = MessageDigest.getInstance(algo);
MessageDigest messagedigest = MessageDigest.getInstance(algorithm);
Stream.of(content).forEach(messagedigest::update);
return messagedigest.digest();
@ -81,8 +81,8 @@ public class EncryptionUtil {
private static byte[] cipherOperation(int operationMode, Key key, byte[] data) {
try {
return createCipherInstance(operationMode, key.getAlgorithm(), key).doFinal(data);
} catch (IllegalBlockSizeException | BadPaddingException illegalblocksizeexception) {
illegalblocksizeexception.printStackTrace();
} catch (IllegalBlockSizeException | BadPaddingException ex) {
ex.printStackTrace();
}
System.err.println("Cipher data failed!");
@ -95,8 +95,8 @@ public class EncryptionUtil {
cipher.init(operationMode, key);
return cipher;
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException invalidkeyexception) {
invalidkeyexception.printStackTrace();
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException ex) {
ex.printStackTrace();
}
System.err.println("Cipher creation failed!");

View File

@ -126,7 +126,7 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
public void sendBungeeActivateMessage(CommandSender sender, String target, boolean activate) {
if (sender instanceof Player) {
notifiyBungeeCord((Player) sender, target, activate, sender instanceof Player);
notifyBungeeCord((Player) sender, target, activate, sender instanceof Player);
} else {
Player firstPlayer = Iterables.getFirst(getServer().getOnlinePlayers(), null);
if (firstPlayer == null) {
@ -134,7 +134,7 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
return;
}
notifiyBungeeCord(firstPlayer, target, activate, sender instanceof Player);
notifyBungeeCord(firstPlayer, target, activate, sender instanceof Player);
}
}
@ -177,7 +177,7 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
}
}
private void notifiyBungeeCord(PluginMessageRecipient sender, String target, boolean activate, boolean isPlayer) {
private void notifyBungeeCord(PluginMessageRecipient sender, String target, boolean activate, boolean isPlayer) {
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
if (activate) {
dataOutput.writeUTF("ON");

View File

@ -7,6 +7,8 @@ import com.github.games647.fastlogin.core.shared.MojangApiConnector;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
@ -19,17 +21,22 @@ import org.json.simple.JSONValue;
public class MojangApiBukkit extends MojangApiConnector {
//mojang api check to prove a player is logged in minecraft and made a join server request
private static final String HAS_JOINED_URL = "https://sessionserver.mojang.com/session/minecraft/hasJoined?";
private static final String HAS_JOINED_URL = "https://sessionserver.mojang.com/session/minecraft/hasJoined?" +
"username=%s&serverId=%s";
public MojangApiBukkit(Logger logger, List<String> localAddresses, int rateLimit, Map<String, Integer> proxies) {
super(logger, localAddresses, rateLimit, proxies);
}
@Override
public boolean hasJoinedServer(LoginSession session, String serverId) {
public boolean hasJoinedServer(LoginSession session, String serverId, InetSocketAddress ip) {
BukkitLoginSession playerSession = (BukkitLoginSession) session;
try {
String url = HAS_JOINED_URL + "username=" + playerSession.getUsername() + "&serverId=" + serverId;
String url = String.format(HAS_JOINED_URL, playerSession.getUsername(), serverId);
if (ip != null) {
url += "&ip=" + URLEncoder.encode(ip.getAddress().getHostAddress(), "UTF-8");
}
HttpURLConnection conn = getConnection(url);
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
@ -54,7 +61,7 @@ public class MojangApiBukkit extends MojangApiConnector {
return true;
}
} catch (Exception ex) {
//catch not only ioexceptions also parse and NPE on unexpected json format
//catch not only io-exceptions also parse and NPE on unexpected json format
logger.log(Level.WARNING, "Failed to verify session", ex);
}

View File

@ -38,7 +38,7 @@ public class CrazyLoginHook implements AuthPlugin<Player> {
playerData.setLoggedIn(true);
String ip = player.getAddress().getAddress().getHostAddress();
//this should be done after login to restore the inventory, unhide players, prevent potential memory leaks...
//this should be done after login to restore the inventory, show players, prevent potential memory leaks...
//from: https://github.com/ST-DDT/CrazyLogin/blob/master/src/main/java/de/st_ddt/crazylogin/CrazyLogin.java#L1948
playerData.resetLoginFails();
player.setFireTicks(0);

View File

@ -54,7 +54,7 @@ public class BungeeCordListener implements PluginMessageListener {
//check if the player is still online or disconnected
Player checkedPlayer = plugin.getServer().getPlayerExact(playerName);
//fail if target player is blacklisted because already authed or wrong bungeecord id
//fail if target player is blacklisted because already authenticated or wrong bungeecord id
if (checkedPlayer != null && !checkedPlayer.hasMetadata(plugin.getName())) {
//bungeecord UUID
long mostSignificantBits = dataInput.readLong();

View File

@ -42,7 +42,7 @@ public class LoginSkinApplyListener implements Listener {
if (plugin.getConfig().getBoolean("forwardSkin")) {
//go through every session, because player.getAddress is null
//loginEvent.getAddress is just a InetAddress not InetSocketAddres, so not unique enough
//loginEvent.getAddress is just a InetAddress not InetSocketAddress, so not unique enough
for (BukkitLoginSession session : plugin.getLoginSessions().values()) {
if (session.getUsername().equals(player.getName())) {
String signature = session.getSkinSignature();

View File

@ -55,7 +55,7 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
}
String ip = player.getAddress().getAddress().getHostAddress();
core.getPendingLogins().put(ip + username, new Object());
core.getPendingLogin().put(ip + username, new Object());
String serverId = source.getServerId();
byte[] verify = source.getVerifyToken();

View File

@ -81,7 +81,7 @@ public class VerifyResponseTask implements Runnable {
String serverId = (new BigInteger(serverIdHash)).toString(16);
String username = session.getUsername();
if (plugin.getCore().getApiConnector().hasJoinedServer(session, serverId)) {
if (plugin.getCore().getApiConnector().hasJoinedServer(session, serverId, fromPlayer.getAddress())) {
plugin.getLogger().log(Level.INFO, "Player {0} has a verified premium account", username);
session.setVerified(true);
@ -177,7 +177,7 @@ public class VerifyResponseTask implements Runnable {
//tell the server that we want to close the connection
player.kickPlayer("Disconnect");
} catch (InvocationTargetException ex) {
plugin.getLogger().log(Level.SEVERE, "Error sending kickpacket", ex);
plugin.getLogger().log(Level.SEVERE, "Error sending kick packet", ex);
}
}

View File

@ -56,7 +56,7 @@ public class ProtocolSupportListener extends JoinManagement<Player, CommandSende
source.setOnlineMode();
String ip = source.getAddress().getAddress().getHostAddress();
plugin.getCore().getPendingLogins().put(ip + username, new Object());
plugin.getCore().getPendingLogin().put(ip + username, new Object());
BukkitLoginSession playerSession = new BukkitLoginSession(username, null, null, registered, profile);
plugin.getLoginSessions().put(source.getAddress().toString(), playerSession);

View File

@ -3,7 +3,7 @@ package com.github.games647.fastlogin.bukkit.tasks;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.shared.ForceLoginMangement;
import com.github.games647.fastlogin.core.shared.ForceLoginManagement;
import com.github.games647.fastlogin.core.shared.LoginSession;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
@ -16,7 +16,7 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.metadata.FixedMetadataValue;
public class ForceLoginTask extends ForceLoginMangement<Player, CommandSender, BukkitLoginSession, FastLoginBukkit> {
public class ForceLoginTask extends ForceLoginManagement<Player, CommandSender, BukkitLoginSession, FastLoginBukkit> {
public ForceLoginTask(FastLoginCore<Player, CommandSender, FastLoginBukkit> core, Player player) {
super(core, player);

View File

@ -3,6 +3,7 @@ package com.github.games647.fastlogin.bungee;
import com.github.games647.fastlogin.core.shared.LoginSession;
import com.github.games647.fastlogin.core.shared.MojangApiConnector;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
@ -35,7 +36,7 @@ public class MojangApiBungee extends MojangApiConnector {
}
@Override
public boolean hasJoinedServer(LoginSession session, String serverId) {
public boolean hasJoinedServer(LoginSession session, String serverId, InetSocketAddress ip) {
//this is not needed in Bungee
throw new UnsupportedOperationException("Not supported");
}

View File

@ -58,7 +58,7 @@ public class PlayerConnectionListener implements Listener {
return;
}
//use the loginevent instead of the postlogin event in order to send the loginsuccess packet to the client
//use the login event instead of the postlogin event in order to send the loginsuccess packet to the client
//with the offline uuid this makes it possible to set the skin then
PendingConnection connection = loginEvent.getConnection();
InitialHandler initialHandler = (InitialHandler) connection;

View File

@ -47,7 +47,7 @@ public class AsyncPremiumCheck extends JoinManagement<ProxiedPlayer, CommandSend
plugin.getSession().put(source.getConnection(), new BungeeLoginSession(username, registered, profile));
String ip = source.getAddress().getAddress().getHostAddress();
plugin.getCore().getPendingLogins().put(ip + username, new Object());
plugin.getCore().getPendingLogin().put(ip + username, new Object());
}
@Override

View File

@ -3,7 +3,7 @@ package com.github.games647.fastlogin.bungee.tasks;
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.shared.ForceLoginMangement;
import com.github.games647.fastlogin.core.shared.ForceLoginManagement;
import com.github.games647.fastlogin.core.shared.LoginSession;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
@ -15,7 +15,7 @@ import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
public class ForceLoginTask extends ForceLoginMangement<ProxiedPlayer, CommandSender, BungeeLoginSession, FastLoginBungee> {
public class ForceLoginTask extends ForceLoginManagement<ProxiedPlayer, CommandSender, BungeeLoginSession, FastLoginBungee> {
private final Server server;

View File

@ -5,7 +5,6 @@ import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@ -36,7 +35,7 @@ public class BalancedSSLFactory extends SSLSocketFactory {
}
@Override
public Socket createSocket(Socket socket, String host, int port, boolean autoclose) throws IOException {
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
return oldFactory.createSocket(host, port, getNextLocalAddress(), 0);
}

View File

@ -65,7 +65,7 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
protected final Map<String, String> localeMessages = new ConcurrentHashMap<>();
private final ConcurrentMap<String, Object> pendingLogins = FastLoginCore.buildCache(5, -1);
private final ConcurrentMap<String, Object> pendingLogin = FastLoginCore.buildCache(5, -1);
private final Set<UUID> pendingConfirms = Sets.newHashSet();
private final T plugin;
@ -186,8 +186,8 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
this.passwordGenerator = passwordGenerator;
}
public ConcurrentMap<String, Object> getPendingLogins() {
return pendingLogins;
public ConcurrentMap<String, Object> getPendingLogin() {
return pendingLogin;
}
public Collection<UUID> getPendingConfirms() {

View File

@ -6,7 +6,7 @@ import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import java.util.logging.Level;
public abstract class ForceLoginMangement<P extends C, C, L extends LoginSession, T extends PlatformPlugin<C>>
public abstract class ForceLoginManagement<P extends C, C, L extends LoginSession, T extends PlatformPlugin<C>>
implements Runnable {
protected final FastLoginCore<P, C, T> core;
@ -14,7 +14,7 @@ public abstract class ForceLoginMangement<P extends C, C, L extends LoginSession
protected L session;
public ForceLoginMangement(FastLoginCore<P, C, T> core, P player) {
public ForceLoginManagement(FastLoginCore<P, C, T> core, P player) {
this.core = core;
this.player = player;
}

View File

@ -29,7 +29,7 @@ public abstract class JoinManagement<P extends C, C, S extends LoginSource> {
profile.setLastIp(ip);
try {
if (profile.getUserId() == -1) {
if (core.getPendingLogins().remove(ip + username) != null && config.get("secondAttemptCracked", false)) {
if (core.getPendingLogin().remove(ip + username) != null && config.get("secondAttemptCracked", false)) {
core.getPlugin().getLogger().log(Level.INFO, "Second attempt login -> cracked {0}", username);
//first login request failed so make a cracked session

View File

@ -112,7 +112,7 @@ public abstract class MojangApiConnector {
return null;
}
public abstract boolean hasJoinedServer(LoginSession session, String serverId);
public abstract boolean hasJoinedServer(LoginSession session, String serverId, InetSocketAddress ip);
protected abstract String getUUIDFromJson(String json);

View File

@ -37,7 +37,7 @@ secondAttemptCracked: false
switchMode: false
# If this plugin detected that a player has a premium, it can also set the associated
# uuid from that account. So if the players changes their usernames, they will still have
# uuid from that account. So if the player changes the username, they will still have
# the same player data (inventory, permissions, ...)
#
# Warning: This also means that the UUID will be different if the player is connecting
@ -49,14 +49,14 @@ switchMode: false
# players could still join the server, because they have different UUID.
#
# Moreover you may want to convert the offline UUID to a premium UUID. This will ensure that the player
# will have the same inventory, permissions, ... if they switched to premium authentification from offline/cracked
# will have the same inventory, permissions, ... if they switched to premium authentication from offline/cracked
# authentication.
#
# This feature requires Cauldron, Spigot or a fork of Spigot (Paper)
premiumUuid: false
# This will make an additional check (only for player names which are not in the database) against the mojang servers
# in order to get the premium UUID. If that premium UUID is in the database, we can assume on sucessful login that the
# in order to get the premium UUID. If that premium UUID is in the database, we can assume on successful login that the
# player changed it's username and we just update the name in the database.
# Examples:
# #### Case 1

View File

@ -8,7 +8,7 @@
# You want to have language template? Visit the Github Wiki here:
# https://github.com/games647/FastLogin/wiki/English
# In order to split a message into seperate lines you could just make a new line, but keep the '
# In order to split a message into separate lines you could just make a new line, but keep the '
# Example:
# bla: '&aFirst line
# Second line
@ -24,10 +24,10 @@
# Switch mode is activated and a new (non-whitelist) cracked player tries to join
switch-kick-message: '&4Only paid minecraft whitelisted accounts are allowed to join this server'
# Player activated premium logins in order to skip offline authentication
# Player activated premium login in order to skip offline authentication
add-premium: '&2Added to the list of premium players'
# Player activated premium logins in order to skip offline authentication
# Player activated premium login in order to skip offline authentication
add-premium-other: '&2Player has been added to the premium list'
# Player is already set be a paid account
@ -73,17 +73,17 @@ wait-on-proxy: '&6Sending request...'
# authentication. In this state the client expects a success packet with a encrypted connection or disconnect packet.
# So we kick the player, if we cannot encrypt the connection. In other situation (example: premium name check),
# the player will be just authenticated as cracked
error-kick: '&4Error occured'
error-kick: '&4Error occurred'
# The server sents a verify token within the premium authentication reqest. If this doesn't match on response,
# The server sends a verify token within the premium authentication request. If this doesn't match on response,
# it could be another client sending malicious packets
invalid-verify-token: '&4Invalid token'
# The client sent no request join server request to the mojang servers which would proof that it's owner of that
# acciunt. Only modified clients would do this.
# account. Only modified clients would do this.
invalid-session: '&4Invalid session'
# The client sent a malicous packet without a login request packet
# The client sent a malicious packet without a login request packet
invalid-requst: '&4Invalid request'
# Message if the bukkit isn't fully started to inject the packets