Use username from Mojang for offline IDs

Affected systems: BungeeCord after name change
Effects: Carry on items, permissions, etc. from the old user account without access to the new one

After a name change it could happen that the client still only
knows the old username and will send it to the server. Mojang will provide us with an up-to-date username that we should use instead.

The username is also sent to Mojang, so that they could verify the use. Therefore exploiting this behavior extensively for arbitrary usernames is not possible.

Related #344
This commit is contained in:
games647
2020-05-25 13:20:34 +02:00
parent 286b755ee3
commit 2bd339b0bf
5 changed files with 51 additions and 34 deletions

View File

@ -94,30 +94,36 @@ public class VerifyResponseTask implements Runnable {
String serverId = EncryptionUtil.getServerIdHashString("", loginKey, serverKey.getPublic()); String serverId = EncryptionUtil.getServerIdHashString("", loginKey, serverKey.getPublic());
String username = session.getUsername(); String requestedUsername = session.getRequestUsername();
InetSocketAddress socketAddress = player.getAddress(); InetSocketAddress socketAddress = player.getAddress();
try { try {
MojangResolver resolver = plugin.getCore().getResolver(); MojangResolver resolver = plugin.getCore().getResolver();
InetAddress address = socketAddress.getAddress(); InetAddress address = socketAddress.getAddress();
Optional<Verification> response = resolver.hasJoined(username, serverId, address); Optional<Verification> response = resolver.hasJoined(requestedUsername, serverId, address);
if (response.isPresent()) { if (response.isPresent()) {
plugin.getLog().info("GameProfile {} has a verified premium account", username); plugin.getLog().info("GameProfile {} has a verified premium account", requestedUsername);
String realUsername = response.get().getName();
if (realUsername == null) {
disconnect("invalid-session", true, "Username field null for {}", requestedUsername);
return;
}
SkinProperty[] properties = response.get().getProperties(); SkinProperty[] properties = response.get().getProperties();
if (properties.length > 0) { if (properties.length > 0) {
session.setSkinProperty(properties[0]); session.setSkinProperty(properties[0]);
} }
session.setVerifiedUsername(realUsername);
session.setUuid(response.get().getId()); session.setUuid(response.get().getId());
session.setVerified(true); session.setVerified(true);
setPremiumUUID(session.getUuid()); setPremiumUUID(session.getUuid());
receiveFakeStartPacket(username); receiveFakeStartPacket(realUsername);
} else { } else {
//user tried to fake a authentication //user tried to fake a authentication
disconnect("invalid-session", true disconnect("invalid-session", true
, "GameProfile {0} ({1}) tried to log in with an invalid session ServerId: {2}" , "GameProfile {0} ({1}) tried to log in with an invalid session ServerId: {2}"
, session.getUsername(), socketAddress, serverId); , session.getRequestUsername(), socketAddress, serverId);
} }
} catch (IOException ioEx) { } catch (IOException ioEx) {
disconnect("error-kick", false, "Failed to connect to session server", ioEx); disconnect("error-kick", false, "Failed to connect to session server", ioEx);
@ -146,7 +152,7 @@ public class VerifyResponseTask implements Runnable {
//check if the verify token are equal to the server sent one //check if the verify token are equal to the server sent one
disconnect("invalid-verify-token", true disconnect("invalid-verify-token", true
, "GameProfile {0} ({1}) tried to login with an invalid verify token. Server: {2} Client: {3}" , "GameProfile {0} ({1}) tried to login with an invalid verify token. Server: {2} Client: {3}"
, session.getUsername(), packetEvent.getPlayer().getAddress(), requestVerify, responseVerify); , session.getRequestUsername(), packetEvent.getPlayer().getAddress(), requestVerify, responseVerify);
return false; return false;
} }

View File

@ -92,9 +92,6 @@ public class ProtocolSupportListener extends JoinManagement<Player, CommandSende
BukkitLoginSession playerSession = new BukkitLoginSession(username, registered, profile); BukkitLoginSession playerSession = new BukkitLoginSession(username, registered, profile);
plugin.putSession(source.getAddress(), playerSession); plugin.putSession(source.getAddress(), playerSession);
if (plugin.getConfig().getBoolean("premiumUuid")) {
source.getLoginStartEvent().setOnlineMode(true);
}
} }
@Override @Override

View File

@ -31,6 +31,12 @@ public class ForceLoginTask extends ForceLoginManagement<Player, CommandSender,
FastLoginBukkit plugin = core.getPlugin(); FastLoginBukkit plugin = core.getPlugin();
player.setMetadata(core.getPlugin().getName(), new FixedMetadataValue(plugin, true)); player.setMetadata(core.getPlugin().getName(), new FixedMetadataValue(plugin, true));
if (session != null && !session.getUsername().equals(player.getName())) {
String playerName = player.getName();
plugin.getLog().warn("Player username {} is not matching session {}", playerName, session.getUsername());
return;
}
super.run(); super.run();
PremiumStatus status = PremiumStatus.CRACKED; PremiumStatus status = PremiumStatus.CRACKED;
@ -73,10 +79,6 @@ public class ForceLoginTask extends ForceLoginManagement<Player, CommandSender,
@Override @Override
public boolean isOnlineMode() { public boolean isOnlineMode() {
if (session == null) { return session.isVerified();
return false;
}
return session.isVerified() && player.getName().equals(session.getUsername());
} }
} }

View File

@ -101,25 +101,26 @@ public class ConnectListener implements Listener {
final PendingConnection connection = loginEvent.getConnection(); final PendingConnection connection = loginEvent.getConnection();
final InitialHandler initialHandler = (InitialHandler) connection; final InitialHandler initialHandler = (InitialHandler) connection;
final String username = initialHandler.getLoginRequest().getData();
if (connection.isOnlineMode()) { if (connection.isOnlineMode()) {
LoginSession session = plugin.getSession().get(connection); LoginSession session = plugin.getSession().get(connection);
session.setUuid(connection.getUniqueId()); LoginResult loginProfile = initialHandler.getLoginProfile();
UUID verifiedUUID = connection.getUniqueId();
session.setUuid(verifiedUUID);
session.setVerifiedUsername(loginProfile.getName());
StoredProfile playerProfile = session.getProfile(); StoredProfile playerProfile = session.getProfile();
playerProfile.setId(connection.getUniqueId()); playerProfile.setId(verifiedUUID);
//bungeecord will do this automatically so override it on disabled option //bungeecord will do this automatically so override it on disabled option
if (!plugin.getCore().getConfig().get("premiumUuid", true)) { if (!plugin.getCore().getConfig().get("premiumUuid", true)) {
String username = loginProfile.getName();
setOfflineId(initialHandler, username); setOfflineId(initialHandler, username);
} }
if (!plugin.getCore().getConfig().get("forwardSkin", true)) { if (!plugin.getCore().getConfig().get("forwardSkin", true)) {
// this is null on offline mode // this is null on offline mode
LoginResult loginProfile = initialHandler.getLoginProfile(); loginProfile.setProperties(emptyProperties);
if (loginProfile != null) {
loginProfile.setProperties(emptyProperties);
}
} }
} }
} }

View File

@ -1,32 +1,42 @@
package com.github.games647.fastlogin.core.shared; package com.github.games647.fastlogin.core.shared;
import com.github.games647.fastlogin.core.StoredProfile; import com.github.games647.fastlogin.core.StoredProfile;
import com.google.common.base.Objects;
import java.util.UUID; import java.util.UUID;
public abstract class LoginSession { public abstract class LoginSession {
private final String username;
private final StoredProfile profile; private final StoredProfile profile;
private String requestUsername;
private String username;
private UUID uuid; private UUID uuid;
protected boolean registered; protected boolean registered;
public LoginSession(String username, boolean registered, StoredProfile profile) { public LoginSession(String requestUsername, boolean registered, StoredProfile profile) {
this.username = username; this.requestUsername = requestUsername;
this.username = requestUsername;
this.registered = registered; this.registered = registered;
this.profile = profile; this.profile = profile;
} }
public String getRequestUsername() {
return requestUsername;
}
public String getUsername() { public String getUsername() {
return username; return username;
} }
public synchronized void setVerifiedUsername(String username) {
this.username = username;
}
/** /**
* This value is always false if we authenticate the player with a cracked authentication * @return This value is always false if we authenticate the player with a cracked authentication
*
* @return
*/ */
public synchronized boolean needsRegistration() { public synchronized boolean needsRegistration() {
return !registered; return !registered;
@ -56,11 +66,12 @@ public abstract class LoginSession {
@Override @Override
public synchronized String toString() { public synchronized String toString() {
return this.getClass().getSimpleName() + '{' + return Objects.toStringHelper(this)
"username='" + username + '\'' + .add("profile", profile)
", profile=" + profile + .add("requestUsername", requestUsername)
", uuid=" + uuid + .add("username", username)
", registered=" + registered + .add("uuid", uuid)
'}'; .add("registered", registered)
.toString();
} }
} }