Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cl_filterstuffcmd bypass problem #1497

Open
serfreeman1337 opened this Issue Apr 7, 2014 · 13 comments

Comments

Projects
None yet
7 participants
@serfreeman1337
Copy link

serfreeman1337 commented Apr 7, 2014

You can bypass any slowhack protection by sending a specified message for the client. This messages can be done by AMX Mod X plugins.

Here is some stocks for AMXX:

stock SendCmd_1( id , text[] ) {
      message_begin( MSG_ONE, 51, _, id )
      write_byte( strlen(text) + 2 )
      write_byte( 10 )
      write_string( text )
      message_end()
}

Or another way with CBuf_Exute using \x0A instead of ';' we can forward cmds to CBuf_AddText.

stock SendCmd_2( id , text[] ) {
   static cmd_line[1024]
   message_begin( MSG_ONE, 9, _, id )
   format( cmd_line , sizeof(cmd_line)-1 , "%s%s" , "^n" , text )
   write_string( cmd_line )
   message_end()
}
@WPMGPRoSToTeMa

This comment has been minimized.

Copy link

WPMGPRoSToTeMa commented Apr 7, 2014

@theAsmodai

This comment has been minimized.

Copy link

theAsmodai commented Apr 9, 2014

The second method does not refer to goldsource engine. For this is a little different:
#268 (comment)

@Shevchik

This comment has been minimized.

Copy link

Shevchik commented Apr 28, 2014

Valve, you should just block all commands from server.

@seroff

This comment has been minimized.

Copy link

seroff commented Jun 4, 2015

Good, Nice!

@satnatantas

This comment has been minimized.

Copy link

satnatantas commented Apr 26, 2016

I confirm it and you'd better make it work.

@SamVanheer

This comment has been minimized.

Copy link

SamVanheer commented Jan 27, 2017

The first approach abuses the director command message to stuff text from the local client's side. Special case code can be written in CL_Parse_Director:

void CL_Parse_Director()
{
  int iSize;
  byte *pbuf;

  //Start new locals - Solokiller
  int old_msg_readcount = msg_readcount;
  qboolean bIsValidCommand = true;
  int command;
  //End new locals - Solokiller

  iSize = MSG_ReadByte();
  pbuf = &net_message.data[msg_readcount];

  //Verify that stufftext commands are not cheating.
  command = MSG_ReadByte();

  if( command == DRC_CMD_STUFFTEXT )
  {
    //Filter command here, never allow client to handle it if it fails check.
    bIsValidCommand = ValidStuffText( MSG_ReadString() );
  }

  //Restore readcount
  msg_readcount = old_msg_readcount;

  if( bIsValidCommand  )
  {
    //This was already here
    ClientDLL_DirectorMessage( iSize, pbuf );
  }

  msg_readcount += iSize;
}

ValidStuffText is used by message 9 (stufftext) already, so the second approach is already secure.

Trivially easy to fix.

EDIT:
Alfred replied to my mail:

Thanks for the report, we will look into it.

@WPMGPRoSToTeMa

This comment has been minimized.

Copy link

WPMGPRoSToTeMa commented Jan 27, 2017

You can't filter only by one received svc_stufftext, because command can be sent partially. You should add some label before server command and after OR better to write commands received from server into another command buffer. And when you execute this special buffer you set some flag like g_isCommandReceivedFromServer and you can check this flag in command handler like CL_Connect_f.

@SamVanheer

This comment has been minimized.

Copy link

SamVanheer commented Jan 27, 2017

Alright, so incoming strings should first be preprocessed to skip newlines that occur at the start.

int __cdecl ValidStuffText(const char *cmd)
{
  while( *cmd && *cmd == '\n' )
    ++cmd;
}

Should probably do a thorough check in the command buffer code to see if any other edge cases like this exist though.

@theAsmodai

This comment has been minimized.

Copy link

theAsmodai commented Jan 27, 2017

Not only '\n': #268 (comment)

@SamVanheer

This comment has been minimized.

Copy link

SamVanheer commented Jan 27, 2017

I see, that's not quite as simple to fix. You could "fake" execute it first to parse out the wait commands, but that's not easy to do in this codebase.
I suppose a special function could be used to execute the commands in a dummy environment so all internal commands like wait are parsed out and translated to the appropriate commands to, say, merge duplicates. You'd then have a list of commands like this:

wait x 5
command connect <args>
wait x 2
command name "you've been hacked"

Basically like that. Then you can just filter out illegal commands by pruning them from the list.

It's costly compared to how it does things now, it might be cheaper to just stick in special commands in front and back that denote when the command came from the server (with special handling for the director message and other exploitable commands in official games), which would make the command processor filter them out.
The commands themselves should not be legal to insert, so any insert calls that don't insert those commands (all of them) should filter them. Ideally i'd say Cbuf_AddText and Cbuf_InsertText should should insert those tokens while filtering them out from the actual inputs, using a global to mark whether it should do so. This will likely increase the buffer usage a bit, so barring a full rewrite this is not easy to fix.

Of course, a complete rewrite is probably saner than trying to work around it. I'd rewrite it to preparse the commands and store the origin of the message (again with special handling for directormessage) so that the parser will filter them out. That would increase memory usage, which means out of memory issues will show up more frequently due to how the engine manages memory. I suppose you could statically allocate command buffers and define a hardcoded number of commands each can have, not sure.

Maybe take Source's version?

EDIT: looks like Source just marks commands with flags to ensure they can only be executed if the necessarily privileges are set:

#define FCVAR_SERVER_CAN_EXECUTE	(1<<28)// the server is allowed to execute this command on clients via ClientCommand/NET_StringCmd/CBaseClientState::ProcessStringCmd.
#define FCVAR_SERVER_CANNOT_QUERY	(1<<29)// If this is set, then the server is not allowed to query this cvar's value (via IServerPluginHelpers::StartQueryCvarValue).
#define FCVAR_CLIENTCMD_CAN_EXECUTE	(1<<30)	// IVEngineClient::ClientCmd is allowed to execute this command. 
						// Note: IVEngineClient::ClientCmd_Unrestricted can run any client command.

// Inserts szCmdString into the command buffer as if it was typed by the client to his/her console.
// Note: Calls to this are checked against FCVAR_CLIENTCMD_CAN_EXECUTE (if that bit is not set, then this function can't change it).
//       Call ClientCmd_Unrestricted to have access to FCVAR_CLIENTCMD_CAN_EXECUTE vars.
virtual void	ClientCmd( const char *szCmdString ) = 0;

// This version does NOT check against FCVAR_CLIENTCMD_CAN_EXECUTE.
virtual void	ClientCmd_Unrestricted( const char *szCmdString ) = 0;
	
// This used to be accessible through the cl_restrict_server_commands cvar.
// By default, Valve games restrict the server to only being able to execute commands marked with FCVAR_SERVER_CAN_EXECUTE.
// By default, mods are allowed to execute any server commands, and they can restrict the server's ability to execute client
// commands with this function.
virtual void	SetRestrictServerCommands( bool bRestrict ) = 0;
	
// If set to true (defaults to true for Valve games and false for others), then IVEngineClient::ClientCmd
// can only execute things marked with FCVAR_CLIENTCMD_CAN_EXECUTE.
virtual void	SetRestrictClientCommands( bool bRestrict ) = 0;

Seeing as these cvars and commands are internal, they could probably be updated to work with flags. Both already have a flags variable, so adding this is possible, although mods that re-purposed free flags might have issues. Whatever Source uses to secure execution can probably be backported.

@WPMGPRoSToTeMa

This comment has been minimized.

Copy link

WPMGPRoSToTeMa commented Jan 27, 2017

You can just write commands received from server into another command buffer and execute it after executing default command buffer.

@SamVanheer

This comment has been minimized.

Copy link

SamVanheer commented Jan 27, 2017

That also works :P

@SamVanheer

This comment has been minimized.

Copy link

SamVanheer commented Mar 22, 2019

Looks like the latest update fixes this using my suggestion as a base, but there are some problems with it.

See #2109, #2108, #2106

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.