POTENTIONAL FIX: etded.x86 getstatus exploit

(system) #21

Here is another fix posted on the SL forums. Maybe some might find use for it:


(Paul) #22

It seems like some server admin did pick this up, with my Tracker I’m currently added to the block list (multiple games are hosted on same server). I’m getting a lot connection time outs.

(gaoesa) #23

I tried both the etded patch and I also used the script from schnoog http://www.wolffiles.de/index.php?forum-showposts-44 .

Neither of them stopped the lagging effects completely.

Here is what I made to the firewall then. It’s a copy paste from a post I made at pbbans forum.

I created 2 chains, Q3_BANNED and Q3_CHECK. My general policy in the firewall is to drop everything except if I have allow rule for it.

The banned chain has the major senders to save some overhead or adding IP masks if needed. Commands to create:
iptables -N Q3_BANNED
iptables -A Q3_BANNED -s <any ip> -j DROP

The protocol and port can be omitted in the user chain because it can be defined in the INPUT chain.
iptables -I INPUT -p udp --dport 27960 -j Q3_BANNED
Adds the ban chain to the top of the INPUT CHAIN for all udp packets to the port 27960. Although I don’t use the port address in it because that way it will drop for both of my servers with only one reference.

The second chain, Q3_CHECK
iptables -N Q3_CHECK
iptables -A Q3_CHECK -m string ! --hex-string “|FF FF FF FF|” --algo bm --from 27 --to 30 -j ACCEPT
iptables -A Q3_CHECK -m string --algo bm --string “getstatus” -m recent --set --name getstatus
iptables -A Q3_CHECK -m recent --update --seconds 2 --hitcount 4 --name getstatus -j DROP
iptables -A Q3_CHECK -j ACCEPT

  1. Creating user chain
  2. Accept all packets that are not “connectionless” by the engine. (i.e. don’t have the 0xFFFFFFFF at the beginning of the UDP data. The match is done for the hole packet, including the IP header and UDP header.)
  3. Match the packet for if it is getstatus packet. If it is, store its source ip with the recent module and name them getstatus.
  4. If during the last 2 seconds there has been 4 getstatus packets, drop the packet.
  5. Accept all the rest of the packets.

Adding the Q3_CHECK chain to the INPUT chain.
iptables -I INPUT 2 -p udp --dport 27960 -j Q3_CHECK
Added the chain to be after the topmost Q3_BANNED chain.

Little explanation

The first rule in the Q3_CHECK chain is to reduce overhead. As far as the engine cares, the “connectionless” command, which the getstatus is, always have 0xFFFFFFFF in the beginning of the data. The connectionless is a term used by the engine as opposed to game packets which never have that (integer -1) pattern in the beginning of the UDP data.

Unfortunately, the tokenising functionality allows there to be any whitespace between the starting 4 bytes of FF and the “getstatus” string. Therefore, the pattern can’t be reliably matched with “\xFF\xFF\xFF\xFFgetstatus”. The engine will accept “\xFF\xFF\xFF\xFF\x01\x02\x03\xFE\xFA … getstatus” as a valid connectionless command and will respond to it.

I used the --hex-string because I found out that in my Linux distribution there isn’t the u32 module which would be a lot better for it. However, I believe it will still reduce overhead over searching for getstatus string from all whole packets. The offset limits the search to the 4 bytes in the beginning of the UDP data.

If there is a option or module that matches only the data field in the UDP diagram it would be usefull. I don’t know, I practically started studying these rules just today.

Since I have 2 server behind the same firewall, I created separated chain for both of them. This way the recent match list wont be accumulating from queries made to both of the servers. With HLSW this can be clearly seen as it starts to block the querys.

So the traffic for the 27960 server will go to Q3_27960 chain that was created with the following commands:
iptables -N Q3_27960
iptables -A Q3_27960 -m string ! --hex-string “|FF FF FF FF|” --algo bm --from 27 --to 30 -j ACCEPT
iptables -A Q3_27960 -m string --algo bm --string “getstatus” -m recent --set --name getstatus_27960
iptables -A Q3_27960 -m recent --update --seconds 2 --hitcount 4 --name getstatus_27960 -j DROP
iptables -A Q3_27960 -j ACCEPT

Notice that the recent list is now named differently in both chains so they wont accumulate.

This seems to work well so far and even better, the etded.x86 will never know about the packets. I’m not an experienced server admin and I studied these rules just because I needed them and I really appreciate all improvements anyone can suggest. I already would have used the u32 module instead of the hex-string match but that was unfortunately not possible without updating the kernel.

(nQw.Wussie) #24

Hi everybody!
Your idea is interesting gaoesa, because your script seems to dynamically ban the offending IPs, instantly and without the need of running a cron job to get it working.
I am currently using another script which logs the bans in a dedicated file and has to be run by a cron job, but I am willing to try yours if you think it would work better with the u32 filter, as my server supports it.
The filter based on the “string” module is an ingenious choice, but I fear it will induce far more cpu overhead than a u32 based filter, so I would prefer to use u32 if possible.
How do you think your script should look like to make use of u32? a small adjustment I suspect…

(gaoesa) #25

Thanks nQw.Wussie

Replacing the
iptables -A Q3_CHECK -m string ! --hex-string “|FF FF FF FF|” --algo bm --from 27 --to 30 -j ACCEPT
iptables -A Q3_CHECK -m u32 ! --u32 “27=0xFFFFFFFF” -j ACCEPT
can possibly work.

Unfortunately I can’t test it. I hope this helps.

(nQw.Wussie) #26

Hmm, so I still can’t get rid of the “string” module alltogether…
I’ll just have to recompile the kernel for “string” and “recent” support and give it a try. Hopefully this weekend I’ll find the time to try this.
Great job, thanks!

(tjimboo) #27

does anyone a solution for windows based servers?

(Scennative) #28

yes, switch to unix. :stuck_out_tongue_winking_eye: Easy, or?

(hellreturn) #29

Requesting same.


(macbeth) #30


since few days our server has some crashes , but we are not sure at all if it is cuz attacks or a linux machine server’s hoster problems
here the events log description
Your server stopped unexpectedly around this time. If you told it to exit in-game, this is nothing to worry about; otherwise, the last 10,000 bytes of output from the server, given below, may provide you with clues on what went wrong.

1 attempted)
FS_Write: 0 bytes written (41 attempted)
FS_Write: 0 bytes written (41 attempted)

/servers/wfxpsave/exec_wfxpsave: line 7: 25501 Segmentation fault (core dumped)

thanks you

(Indloon) #31

Can someone say where the getstatus code is in ET-GPL?
Since I haven’t got any reply from jRAD or digibob.
I looked to server/sv_ccmds.c.Nop,nothing there except:

static void SV_Status_f( void ) {
	int i, j, l;
	client_t    *cl;
	playerState_t   *ps;
	const char      *s;
	int ping;

	// make sure server is running
	if ( !com_sv_running->integer ) {
		Com_Printf( "Server is not running.
" );

	Com_Printf( "map: %s
", sv_mapname->string );

	Com_Printf( "num score ping name            lastmsg address               qport rate
" );
	Com_Printf( "--- ----- ---- --------------- ------- --------------------- ----- -----
" );
	for ( i = 0,cl = svs.clients ; i < sv_maxclients->integer ; i++,cl++ )
		if ( !cl->state ) {
		Com_Printf( "%3i ", i );
		ps = SV_GameClientNum( i );
		Com_Printf( "%5i ", ps->persistant[PERS_SCORE] );

		if ( cl->state == CS_CONNECTED ) {
			Com_Printf( "CNCT " );
		} else if ( cl->state == CS_ZOMBIE ) {
			Com_Printf( "ZMBI " );
		} else
			ping = cl->ping < 9999 ? cl->ping : 9999;
			Com_Printf( "%4i ", ping );

		Com_Printf( "%s", cl->name );
		l = 16 - strlen( cl->name );
		for ( j = 0 ; j < l ; j++ )
			Com_Printf( " " );

		Com_Printf( "%7i ", svs.time - cl->lastPacketTime );

		s = NET_AdrToString( cl->netchan.remoteAddress );
		Com_Printf( "%s", s );
		l = 22 - strlen( s );
		for ( j = 0 ; j < l ; j++ )
			Com_Printf( " " );

		Com_Printf( "%5i", cl->netchan.qport );

		Com_Printf( " %5i", cl->rate );

		Com_Printf( "
" );
	Com_Printf( "
" );

Which seems to be for only client side,not for requesting side.
Tbh,not sure if right,but in sv_init.c
Seems to be some nice parts…

(schnoog) #32

the getstatus query is handled in src/server/sv_main.c


Responds with all the info that qplug or qspy can see about the server
and all connected players.  Used for getting detailed information after
the simple info query.
void SVC_Status( netadr_t from ) {
	char player[1024];
	char status[MAX_MSGLEN];
	int i;
	client_t    *cl;
	playerState_t   *ps;
	int statusLength;
	int playerLength;
	char infostring[MAX_INFO_STRING];

	// ignore if we are in single player
	if ( SV_GameIsSinglePlayer() ) {

	//bani - bugtraq 12534
	if ( !SV_VerifyChallenge( Cmd_Argv( 1 ) ) ) {

	strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO | CVAR_SERVERINFO_NOUPDATE ) );

	// echo back the parameter to status. so master servers can use it as a challenge
	// to prevent timed spoofed reply packets that add ghost servers
	Info_SetValueForKey( infostring, "challenge", Cmd_Argv( 1 ) );

	// add "demo" to the sv_keywords if restricted
	if ( Cvar_VariableValue( "fs_restrict" ) ) {
		char keywords[MAX_INFO_STRING];

		Com_sprintf( keywords, sizeof( keywords ), "ettest %s",
					 Info_ValueForKey( infostring, "sv_keywords" ) );
		Info_SetValueForKey( infostring, "sv_keywords", keywords );

	status[0] = 0;
	statusLength = 0;

	for ( i = 0 ; i < sv_maxclients->integer ; i++ ) {
		cl = &svs.clients[i];
		if ( cl->state >= CS_CONNECTED ) {
			ps = SV_GameClientNum( i );
			Com_sprintf( player, sizeof( player ), "%i %i \"%s\"
						 ps->persistant[PERS_SCORE], cl->ping, cl->name );
			playerLength = strlen( player );
			if ( statusLength + playerLength >= sizeof( status ) ) {
				break;      // can't hold any more
			strcpy( status + statusLength, player );
			statusLength += playerLength;

	NET_OutOfBandPrint( NS_SERVER, from, "statusResponse
%s", infostring, status );

(Indloon) #33

Now must add:
Com_sprintf( KILLS,DEATHS,CLASS,GUID!! ) :smiley:

IT should look like this,since I haven’t learned C,but its very similar to PHP.

	char class[1024]; //G - The size of strings allowed,should't be 10??
	char GUID; //G - The GUID contains of chars and ints
	int kills; //G 
	int deaths; //G


Com_sprintf( player, sizeof( player ), "%i %i %i %i \"%s %s %s\"
",ps->persistant[PERS_SCORE], ps->persistant[KILLS], ps->persistant[DEATHS], cl->ping, cl->class, cl->GUID, cl->name,  );

I’m correct or absolutly wrong?:d

	client_t    *cl; 
	playerState_t   *ps; 

In witch file(s) I can look for these functions??

(schnoog) #34

Genert, dont forget to trim the GUID to not include the full once (spoofing would be very easy if even the getstatus returns the full GUID of each player.
Unfortunality I also dont be familar with “C” ,but for sure: If youre able to write php code, youre able to read C code.

Furtheron I dont know where to find the function you looking for, but grep is your friend for that.

grep -r 'playerState_t' *

(Indloon) #35


Maybe to encrypt the GUID?

MD5 :smiley: nah,that could be too easy:P

Some kind new encrypting method,like apache kind of .htaccess pw way.

(schnoog) #36

The spoofing risk is mainly thought of getting userlevels on a server. But if you want to hash it, why not use a “salt” (not a real salt, which would change from client to client), simply add for example a hardcoded long char-string. This would blow up the raw-string massivly, so even a rainbow-table offence is not economical (it`s not a high-risk, ET is wether NASA nor CIA nor MS, so nobody (who is not a absurd idiot) will invest billion hours of computing time to build up a MD5 rainbowtable with 30+ chars lenght.

(inbredhill) #37

what is a typical iptables ruleset look like that fixes a lot of these hack/attack attempts?

I am finding that the suggested fixes seem to cause a memory leak in the scripts themselves.

(schnoog) #38

Hi inbredhill,
unfortunality the source ip is spoofed for such attacks and changes every few hours.
I suggest you to use the script wrote by OldMan : http://wolffiles.de/index.php?forum-showposts-44-p5#
The script, runned by cron all 5 minutes brought my outgoiing traffic from 18MB/s down to a few kB/s

(inbredhill) #39

[QUOTE=schnoog;382191]Hi inbredhill,
unfortunality the source ip is spoofed for such attacks and changes every few hours.
I suggest you to use the script wrote by OldMan : http://wolffiles.de/index.php?forum-showposts-44-p5#
The script, runned by cron all 5 minutes brought my outgoiing traffic from 18MB/s down to a few kB/s[/QUOTE]

I am running that script. My only problem is that either that script, csf, or the both are creating a slowly growing memory leak. I have it set to run once every 30 minutes.

You may want to add an iptables rule to block ips that attempt brute force ssh login attempts.

(schnoog) #40

try for example denyhosts to stop those ssh bfs with hosts.deny file. With global ban list :wink:
Use it since years

csf (config blabla firewall) ? maybe I find the time to rewrite V1.5 to fit csf needings