mirror of
https://git.neolegacy.dev/neoStudiosLCE/neoLegacy.git
synced 2026-07-17 19:40:45 +00:00
feat: dedicated server security hardening
Comprehensive security system to protect against packet-sniffing attacks, XUID harvesting, privilege escalation, bot flooding, and XUID impersonation. - Stream cipher: per-session XOR cipher with 4-message handshake via CustomPayloadPacket (MC|CKey, MC|CAck, MC|COn). Negotiated per-connection, backwards compatible (old clients/servers fall back to plaintext). - Security gate: buffers all game data until cipher handshake completes, preventing unsecured clients from receiving any XUIDs or game state. - Cipher handshake enforcer: kicks clients that don't complete the handshake within 5 seconds (configurable via require-secure-client). - Identity tokens: persistent per-XUID tokens in identity-tokens.json, issued over the encrypted channel, verified on reconnect. Prevents XUID replay attacks. Client stores server-specific tokens. - PROXY protocol v1: parses real client IPs from playit.gg tunnel headers so rate limiting, IP bans, and XUID spoof detection work per-player. - Rate limiting: per-IP sliding window (default 5 connections/30s) with pending connection cap (default 10). - Privilege hardening: OP requires ops.json, live checks on every command and privilege packet. Host-only server settings changes. - XUID stripping: PreLoginPacket response sends INVALID_XUID placeholders. - Packet validation: readUtf global string cap, reduced max packet size, stream desync protection on oversized strings. - OpManager: persistent ops.json with XUID-based OP list. - Whitelist improvements: whitelist add accepts player names with ambiguity detection, XUID cache from login attempts. - revoketoken command: revoke identity tokens for players who lost theirs. - server.log: persistent log file written alongside console output with flush-per-write to survive crashes. - CLI security logging: consolidated per-join security summary with cipher status, token status, XUID, and real IP. Security warnings for kicks, spoofing, and unauthorized commands.
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
#include "commands\tp\CliCommandTp.h"
|
||||
#include "commands\weather\CliCommandWeather.h"
|
||||
#include "commands\whitelist\CliCommandWhitelist.h"
|
||||
#include "commands\revoketoken\CliCommandRevokeToken.h"
|
||||
#include "..\Common\StringUtils.h"
|
||||
#include "..\ServerShutdown.h"
|
||||
#include "..\ServerLogger.h"
|
||||
@@ -100,6 +101,7 @@ namespace ServerRuntime
|
||||
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandPardonIp()));
|
||||
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandBanList()));
|
||||
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandWhitelist()));
|
||||
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandRevokeToken()));
|
||||
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandTp()));
|
||||
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandTime()));
|
||||
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandWeather()));
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "CliCommandRevokeToken.h"
|
||||
|
||||
#include "..\..\ServerCliEngine.h"
|
||||
#include "..\..\ServerCliParser.h"
|
||||
#include "..\..\..\Access\Access.h"
|
||||
#include "..\..\..\Security\IdentityTokenManager.h"
|
||||
#include "..\..\..\ServerLogManager.h"
|
||||
|
||||
namespace ServerRuntime
|
||||
{
|
||||
const char *CliCommandRevokeToken::Name() const
|
||||
{
|
||||
return "revoketoken";
|
||||
}
|
||||
|
||||
const char *CliCommandRevokeToken::Usage() const
|
||||
{
|
||||
return "revoketoken <name|xuid>";
|
||||
}
|
||||
|
||||
const char *CliCommandRevokeToken::Description() const
|
||||
{
|
||||
return "Revoke a player's identity token. They will be issued a new one on next login.";
|
||||
}
|
||||
|
||||
bool CliCommandRevokeToken::Execute(const ServerCliParsedLine &line, ServerCliEngine *engine)
|
||||
{
|
||||
if (line.tokens.size() < 2)
|
||||
{
|
||||
engine->LogWarn(std::string("Usage: ") + Usage());
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerUID xuid = INVALID_XUID;
|
||||
|
||||
// Try parsing as XUID first
|
||||
if (ServerRuntime::Access::TryParseXuid(line.tokens[1], &xuid))
|
||||
{
|
||||
// Direct XUID
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try name lookup from cache
|
||||
std::vector<PlayerUID> cachedXuids;
|
||||
int count = ServerRuntime::ServerLogManager::GetCachedXuids(line.tokens[1], &cachedXuids);
|
||||
if (count == 0)
|
||||
{
|
||||
engine->LogWarn("Unknown player: " + line.tokens[1]);
|
||||
engine->LogWarn("The player must have attempted to connect, or use: revoketoken <xuid>");
|
||||
return false;
|
||||
}
|
||||
if (count > 1)
|
||||
{
|
||||
engine->LogWarn("Ambiguous: " + std::to_string(count) + " XUIDs seen for '" + line.tokens[1] + "':");
|
||||
for (size_t i = 0; i < cachedXuids.size(); ++i)
|
||||
{
|
||||
std::string label = (i == cachedXuids.size() - 1) ? " (most recent)" : "";
|
||||
engine->LogWarn(" " + ServerRuntime::Access::FormatXuid(cachedXuids[i]) + label);
|
||||
}
|
||||
engine->LogWarn("Re-run with the explicit XUID: revoketoken <xuid>");
|
||||
return false;
|
||||
}
|
||||
xuid = cachedXuids.back();
|
||||
engine->LogInfo("Resolved '" + line.tokens[1] + "' to XUID " + ServerRuntime::Access::FormatXuid(xuid));
|
||||
}
|
||||
|
||||
if (!ServerRuntime::Security::GetIdentityTokenManager().HasToken(xuid))
|
||||
{
|
||||
engine->LogWarn("No identity token found for XUID " + ServerRuntime::Access::FormatXuid(xuid));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ServerRuntime::Security::GetIdentityTokenManager().RevokeToken(xuid))
|
||||
{
|
||||
engine->LogError("Failed to revoke token.");
|
||||
return false;
|
||||
}
|
||||
|
||||
engine->LogInfo("Revoked identity token for XUID " + ServerRuntime::Access::FormatXuid(xuid) +
|
||||
". Player will receive a new token on next login.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "..\IServerCliCommand.h"
|
||||
|
||||
namespace ServerRuntime
|
||||
{
|
||||
class CliCommandRevokeToken : public IServerCliCommand
|
||||
{
|
||||
public:
|
||||
virtual const char *Name() const;
|
||||
virtual const char *Usage() const;
|
||||
virtual const char *Description() const;
|
||||
virtual bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine);
|
||||
};
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "..\..\..\Access\Access.h"
|
||||
#include "..\..\..\Common\StringUtils.h"
|
||||
#include "..\..\..\ServerProperties.h"
|
||||
#include "..\..\..\ServerLogManager.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -181,14 +182,44 @@ namespace ServerRuntime
|
||||
{
|
||||
if (line.tokens.size() < 3)
|
||||
{
|
||||
engine->LogWarn("Usage: whitelist add <xuid> [name ...]");
|
||||
engine->LogWarn("Usage: whitelist add <xuid|name> [display name ...]");
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerUID xuid = INVALID_XUID;
|
||||
if (!TryParseWhitelistXuid(line.tokens[2], engine, &xuid))
|
||||
std::string name;
|
||||
|
||||
if (ServerRuntime::Access::TryParseXuid(line.tokens[2], &xuid))
|
||||
{
|
||||
return false;
|
||||
// Argument is a XUID
|
||||
name = StringUtils::JoinTokens(line.tokens, 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Argument is a player name -- look up XUID from recent login cache
|
||||
std::vector<PlayerUID> cachedXuids;
|
||||
int count = ServerRuntime::ServerLogManager::GetCachedXuids(line.tokens[2], &cachedXuids);
|
||||
if (count == 0)
|
||||
{
|
||||
engine->LogWarn("Unknown player: " + line.tokens[2]);
|
||||
engine->LogWarn("The player must attempt to connect first so the server can learn their XUID.");
|
||||
engine->LogWarn("Alternatively, use: whitelist add <xuid>");
|
||||
return false;
|
||||
}
|
||||
if (count > 1)
|
||||
{
|
||||
engine->LogWarn("Ambiguous: " + std::to_string(count) + " different XUIDs have been seen for '" + line.tokens[2] + "':");
|
||||
for (size_t i = 0; i < cachedXuids.size(); ++i)
|
||||
{
|
||||
std::string label = (i == cachedXuids.size() - 1) ? " (most recent)" : "";
|
||||
engine->LogWarn(" " + ServerRuntime::Access::FormatXuid(cachedXuids[i]) + label);
|
||||
}
|
||||
engine->LogWarn("Re-run with the explicit XUID: whitelist add <xuid> [name]");
|
||||
return false;
|
||||
}
|
||||
xuid = cachedXuids.back();
|
||||
name = line.tokens[2];
|
||||
engine->LogInfo("Resolved '" + name + "' to XUID " + ServerRuntime::Access::FormatXuid(xuid));
|
||||
}
|
||||
|
||||
if (ServerRuntime::Access::IsPlayerWhitelisted(xuid))
|
||||
@@ -198,7 +229,6 @@ namespace ServerRuntime
|
||||
}
|
||||
|
||||
const auto metadata = ServerRuntime::Access::WhitelistManager::BuildDefaultMetadata("Console");
|
||||
const auto name = StringUtils::JoinTokens(line.tokens, 3);
|
||||
if (!ServerRuntime::Access::AddWhitelistedPlayer(xuid, name, metadata))
|
||||
{
|
||||
engine->LogError("Failed to write whitelist entry.");
|
||||
|
||||
Reference in New Issue
Block a user