[Server] Fix unauthenticated crash via replay submit code (#7072)

cmdReplaySubmitCode dereferenced userInfo without an authentication
guard, allowing an unauthenticated connection with a valid replay code
to segfault the server. Add the same authState != PasswordRight guard
used by all other replay handlers, and gate session command dispatch on
a pre-auth whitelist so future handlers cannot be reached before login.

Took 2 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-08-14 08:56:54 +02:00 committed by GitHub
parent 7971ebfe94
commit d93f63050c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 52 additions and 27 deletions

View file

@ -133,6 +133,22 @@ void Server_ProtocolHandler::sendProtocolItem(const RoomEvent &item)
Response::ResponseCode Server_ProtocolHandler::processSessionCommandContainer(const CommandContainer &cont, Response::ResponseCode Server_ProtocolHandler::processSessionCommandContainer(const CommandContainer &cont,
ResponseContainer &rc) ResponseContainer &rc)
{ {
const auto isPreAuthSessionCommand = [](SessionCommand::SessionCommandType type) {
switch (type) {
case SessionCommand::PING:
case SessionCommand::LOGIN:
case SessionCommand::REGISTER:
case SessionCommand::ACTIVATE:
case SessionCommand::FORGOT_PASSWORD_REQUEST:
case SessionCommand::FORGOT_PASSWORD_RESET:
case SessionCommand::FORGOT_PASSWORD_CHALLENGE:
case SessionCommand::REQUEST_PASSWORD_SALT:
return true;
default:
return false;
}
};
Response::ResponseCode finalResponseCode = Response::RespOk; Response::ResponseCode finalResponseCode = Response::RespOk;
for (int i = cont.session_command_size() - 1; i >= 0; --i) { for (int i = cont.session_command_size() - 1; i >= 0; --i) {
Response::ResponseCode resp = Response::RespInvalidCommand; Response::ResponseCode resp = Response::RespInvalidCommand;
@ -141,33 +157,38 @@ Response::ResponseCode Server_ProtocolHandler::processSessionCommandContainer(co
if (num != SessionCommand::PING) { // don't log ping commands if (num != SessionCommand::PING) { // don't log ping commands
logDebugMessage(getSafeDebugString(sc)); logDebugMessage(getSafeDebugString(sc));
} }
switch ((SessionCommand::SessionCommandType)num) { const auto commandType = static_cast<SessionCommand::SessionCommandType>(num);
case SessionCommand::PING: if (authState == NotLoggedIn && !isPreAuthSessionCommand(commandType)) {
resp = cmdPing(sc.GetExtension(Command_Ping::ext), rc); resp = Response::RespLoginNeeded;
break; } else {
case SessionCommand::LOGIN: switch (commandType) {
resp = cmdLogin(sc.GetExtension(Command_Login::ext), rc); case SessionCommand::PING:
break; resp = cmdPing(sc.GetExtension(Command_Ping::ext), rc);
case SessionCommand::MESSAGE: break;
resp = cmdMessage(sc.GetExtension(Command_Message::ext), rc); case SessionCommand::LOGIN:
break; resp = cmdLogin(sc.GetExtension(Command_Login::ext), rc);
case SessionCommand::GET_GAMES_OF_USER: break;
resp = cmdGetGamesOfUser(sc.GetExtension(Command_GetGamesOfUser::ext), rc); case SessionCommand::MESSAGE:
break; resp = cmdMessage(sc.GetExtension(Command_Message::ext), rc);
case SessionCommand::GET_USER_INFO: break;
resp = cmdGetUserInfo(sc.GetExtension(Command_GetUserInfo::ext), rc); case SessionCommand::GET_GAMES_OF_USER:
break; resp = cmdGetGamesOfUser(sc.GetExtension(Command_GetGamesOfUser::ext), rc);
case SessionCommand::LIST_ROOMS: break;
resp = cmdListRooms(sc.GetExtension(Command_ListRooms::ext), rc); case SessionCommand::GET_USER_INFO:
break; resp = cmdGetUserInfo(sc.GetExtension(Command_GetUserInfo::ext), rc);
case SessionCommand::JOIN_ROOM: break;
resp = cmdJoinRoom(sc.GetExtension(Command_JoinRoom::ext), rc); case SessionCommand::LIST_ROOMS:
break; resp = cmdListRooms(sc.GetExtension(Command_ListRooms::ext), rc);
case SessionCommand::LIST_USERS: break;
resp = cmdListUsers(sc.GetExtension(Command_ListUsers::ext), rc); case SessionCommand::JOIN_ROOM:
break; resp = cmdJoinRoom(sc.GetExtension(Command_JoinRoom::ext), rc);
default: break;
resp = processExtendedSessionCommand(num, sc, rc); case SessionCommand::LIST_USERS:
resp = cmdListUsers(sc.GetExtension(Command_ListUsers::ext), rc);
break;
default:
resp = processExtendedSessionCommand(num, sc, rc);
}
} }
if (resp != Response::RespOk) { if (resp != Response::RespOk) {
finalResponseCode = resp; finalResponseCode = resp;

View file

@ -896,6 +896,10 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReplayGetCode(const Com
Response::ResponseCode AbstractServerSocketInterface::cmdReplaySubmitCode(const Command_ReplaySubmitCode &cmd, Response::ResponseCode AbstractServerSocketInterface::cmdReplaySubmitCode(const Command_ReplaySubmitCode &cmd,
ResponseContainer & /*rc*/) ResponseContainer & /*rc*/)
{ {
if (authState != PasswordRight) {
return Response::RespFunctionNotAllowed;
}
// code is of the form <game-id>-<hash> // code is of the form <game-id>-<hash>
QString code = QString::fromStdString(cmd.replay_code()); QString code = QString::fromStdString(cmd.replay_code());
QStringList split = code.split("-"); QStringList split = code.split("-");