Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 62 additions & 19 deletions addons/sourcemod/scripting/ServerCommandFilter.sp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public Plugin myinfo =
name = "ServerCommandFilter",
author = "BotoX, .Rushaway, koen",
description = "Filters server commands using user-defined rules for maps (point_servercommand/VScript)",
version = "1.2.0",
version = "1.3.0",
url = "https://github.com/srcdslab/sm-plugin-ServerCommandFilter"
};

Expand Down Expand Up @@ -188,35 +188,53 @@ public MRESReturn AcceptInput(int pThis, Handle hReturn, Handle hParams)
if(!StrEqual(szInputName, "Command", true))
return MRES_Ignored;

int bReplaced = 0;
int client = 0;
if(!DHookIsNullParam(hParams, 2))
client = DHookGetParam(hParams, 2);

char sCommand[COMMAND_SIZE];
DHookGetParamObjectPtrString(hParams, 4, 0, ObjectValueType_String, sCommand, sizeof(sCommand));

int bReplaced = 0;
if (SanitizeSayCommand(sCommand, sizeof(sCommand)))
bReplaced = 1;

if(client > 0 && client <= MaxClients && IsClientInGame(client))
{
char sName[MAX_NAME_LENGTH];
GetClientName(client, sName, sizeof(sName));

char sSteamId[32];
GetClientAuthId(client, AuthId_Engine, sSteamId, sizeof(sSteamId));
if (StrContains(sCommand, "!activator") != -1)
{
if (StrContains(sCommand, "!activator.name") != -1)
{
char sName[MAX_NAME_LENGTH];
GetClientName(client, sName, sizeof(sName));
SanitizePlayerName(sName, sizeof(sName));
bReplaced += ReplaceString(sCommand, sizeof(sCommand), "!activator.name", sName, false);
}

char sUserID[32];
FormatEx(sUserID, sizeof(sUserID), "#%d", GetClientUserId(client));
if (StrContains(sCommand, "!activator.steamid") != -1)
{
char sSteamId[32];
GetClientAuthId(client, AuthId_Engine, sSteamId, sizeof(sSteamId));
bReplaced += ReplaceString(sCommand, sizeof(sCommand), "!activator.steamid", sSteamId, false);
}

char sTeam[32];
if(GetClientTeam(client) == CS_TEAM_CT)
strcopy(sTeam, sizeof(sTeam), "@ct");
else if(GetClientTeam(client) == CS_TEAM_T)
strcopy(sTeam, sizeof(sTeam), "@t");
if (StrContains(sCommand, "!activator.team") != -1)
{
char sTeam[32];
int iTeam = GetClientTeam(client);
if(iTeam == CS_TEAM_CT)
strcopy(sTeam, sizeof(sTeam), "@ct");
else if(iTeam == CS_TEAM_T)
strcopy(sTeam, sizeof(sTeam), "@t");
else strcopy(sTeam, sizeof(sTeam), "@spec");

bReplaced += ReplaceString(sCommand, sizeof(sCommand), "!activator.team", sTeam, false);
}

bReplaced += ReplaceString(sCommand, sizeof(sCommand), "!activator.name", sName, false);
bReplaced += ReplaceString(sCommand, sizeof(sCommand), "!activator.steamid", sSteamId, false);
bReplaced += ReplaceString(sCommand, sizeof(sCommand), "!activator.team", sTeam, false);
bReplaced += ReplaceString(sCommand, sizeof(sCommand), "!activator", sUserID, false);
char sUserID[32];
FormatEx(sUserID, sizeof(sUserID), "#%d", GetClientUserId(client));
bReplaced += ReplaceString(sCommand, sizeof(sCommand), "!activator", sUserID, false);
}
}

Action iAction = ValidateCommand(sCommand, "point_servercommand");
Expand All @@ -228,14 +246,39 @@ public MRESReturn AcceptInput(int pThis, Handle hReturn, Handle hParams)
}
else if(iAction == Plugin_Changed || bReplaced)
{
ServerCommand(sCommand);
ServerCommand("%s", sCommand);
DHookSetReturn(hReturn, true);
return MRES_Supercede;
}

return MRES_Ignored;
}

/**
* Sanitizes "say" and "say_team" commands to prevent command injection via player names.
* Returns true if the command was modified.
*/
bool SanitizeSayCommand(char[] sCommand, int maxlen)
{
// Only sanitize chat commands
if (strncmp(sCommand, "say ", 4, false) != 0 && strncmp(sCommand, "say_team ", 9, false) != 0)
return false;

return SanitizePlayerName(sCommand, maxlen);
}
Comment on lines +261 to +268
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SanitizeSayCommand function is only called in AcceptInput (line 199), but not in the Detour_SendToServerConsole or Detour_SetValue functions. This means that if VScript uses SendToConsole("say " + playerName + " message"), the command will not be sanitized for command injection. While the PR description claims "global protection" for VScript string concatenation, this only applies to commands going through point_servercommand, not through SendToConsole. Consider adding the same sanitization to Detour_SendToServerConsole to ensure consistent protection across all command sources.

Copilot uses AI. Check for mistakes.

/**
* Sanitizes a player name to prevent command injection.
*/
bool SanitizePlayerName(char[] sName, int maxlen)
{
bool bChanged = false;
bChanged |= ReplaceString(sName, maxlen, ";", " ", false) > 0;
bChanged |= ReplaceString(sName, maxlen, "\"", "", false) > 0;
bChanged |= ReplaceString(sName, maxlen, "\n", " ", false) > 0;
bChanged |= ReplaceString(sName, maxlen, "\r", " ", false) > 0;
return bChanged;
}

/**
* Generic validation function that can be used by both AcceptInput and SetValue
Expand Down