Teamspeak 3 RCE advisory by:
ff214370685e536b9ee021c7ff6b7680bfbe6008bc29f87511b6b90256043536
August 10, 2016
While auditing the Teamspeak 3 server I've discovered several 0-day
vulnerabilities which I'll describe in detail in this advisory. They exist in
the newest version of the server, version 3.0.13.
I found 10 vulnerabilities. Some of these are critical and allow remote code
execution. For the average user, that means that these vulnerabilities can be
exploited by a malicious attacker in order to take over any Teamspeak server,
not only becoming serveradmin, but getting a shell on the affected machine.
Here's the output of an exploit which uses two of the vulnerabilities:
$ python exploit_teamspeak.py
leaking distinct stack pointers
'\xa2' '\x9a' '\x8a' . '_' .. '\xa0'
got a ptr: 0x7fa29a8a5fa0
'\xa2' '\x9a' '\x9a' 'o' ... '\xa0'
got a ptr: 0x7fa29a9a6fa0
'\xa2' '\x9a' '\xaa' . '\x7f' '\xa0'
got a ptr: 0x7fa29aaa7fa0
stack ptr: 0x7fa29a8a5fa0
assumed stack base: 0x7fa29a5a5000
sleeping a bit to avoid flood detection.......
initializing stack sprayers............
spraying the stacks............
doing some magic.....
Got a shell from ('127.0.0.1', 38416)
ts3@ts3:/home/ts3/teamspeak3-server$
I won't release the exploit anytime soon, but I will note that writing one is
a great learning experience.
Next I'll describe my findings. I'll be referring to function names. The
Teamspeak developers strip their binaries of symbols, but they messed up once
and forgot to do so.
If you want to follow along at home, I'm sure your favorite search engine can
help you find the non-stripped server binary.
Now on to the vulns!
--- vuln 1: race condition leading to use-after-free ---
The ts3 server is threaded. When accessing objects like a Client or a Channel,
which can be shared among threads, it's necessary to hold a mutex. However the
function VirtualServerBase::sendCommandLowPacket drops its mutex before
accessing a Client object. Here's the vulnerable code:
0x49d26d:
call _pthread_mutex_unlock
mov rdi, client
; this will mov rax, [rdi+0F0h]
call Client::getTransmissionReceiveBase(void)
mov rcx, [rax]
mov rdx, [rbx+VirtualServer.vsb.vserv_id]
mov rdi, rax
mov rsi, r14
call qword ptr [rcx+58h]
As we can see, the mutex is unlocked and then a TransmissionReceiveBase struct
is taken out of the Client. Then its vtable is used for a call. Looking at
the kernel source we see that, at least on Linux, _pthread_mutex_unlock will
swap out the current thread if there's another thread blocked waiting for the
mutex.
This other thread could then free the Client and place controlled data on top
of the freed block. When the first thread runs again, we control the
TransmissionReceiveBase object completely. The indirect call through its
vtable allows us to get $pc.
This is one of the vulnerabilities used in the exploit above.
--- vuln 2: disclosure of a partially uninitialized stack buffer ---
When a client first connects to the server, it sends over an IV. The IV is
base64-encoded. The server decodes it in VirtualServerBase::clientInitIV.
However the server ignores the return value of Crypt::decodeBase64 which is
the decoded length. Instead it assumes that the length is always 10 bytes.
If the client only encodes e.g. 9 bytes and sends them over, one byte of the
IV will be uninitialized. The client can guess this byte. Only a correct guess
will prevent later cryptographic operations from failing. Thus the client can
deduce the byte. It can repeat the process, sending over only 8 bytes, etc.
This lets us do a byte-at-a-time leak from the stack.
Specifically it lets us leak a stack pointer, beating ASLR. This was used in
the exploit above.
--- vuln 3: disclosure of heap memory ---
The ts3 server compresses command packets with the "qlz" library. However a
known vulnerability resides in one of the older versions of this library.
This was already fixed in the newest qlz release (a beta release). However the
ts3 server uses an older version of this library.
The vulnerability is described here:
http://blog.frama-c.com/index.php?post/2011/04/05/QuickLZ-1
But it's straightforward to find for anyone looking at qlz's source code. You
don't need any fancy "software analyzer" to find it, like in that silly blog
post.
The vulnerability is qlz-specific and essentially allows you to "decompress"
data, but much of the decompressed data is actually uninitialized.
Why is this a problem for the ts3 server? Because we can send a short
compressed command packet over which starts with "some_command return_code=".
When we use this vuln and the packet is decompressed, the following bytes will
be included in the packet. So the packet that ts3 sees is "some_command
return_code=<bytes from the heap>".
Since the return_code parameter is reflected back to us, this lets us leak
data from the heap.
-----
Those three vulnerabilities were the critical ones. Below I'll briefly
describe some minor ones, resulting in DoS or more interesting things.
--- vuln 4: check if any file exists on the server ---
The function VirtualServer::getpermission_fileTransferInitDownload accepts a
path from the client and gives different error messages depending on whether
the file exists or not.
For example using "/../../../etc/passwd" gives "bad path" while
"/../../../etc/passwz" gives "no such file". This can be used to determine the
remote OS and installed software, enumerate open fds, etc.
--- vulns 5, 6, 7, 8: race conditions galore ---
The function PermissionManager::getChannelGroupInherited looks up and uses a
Channel struct without holding any mutex. The channel is only used to look up
a channelid, so this is an infoleak/DoS at best.
In the function VirtualServer::sendRemoteDebuggingInfo we look up a Client
struct, then drop the mutex, and then we proceed to use the Client to look up
its uuid. Again this is an infoleak/DoS at best.
In ServerParser::cmd_permget we look up a Client without holding any mutex.
In ServerParser::cmd_clientsetserverquerylogin we do the same.
--- vuln 9: ParameterParser index-out-of-bounds DoS ---
In PacketHandler::inINITPacket_SolvePuzzle we initialize a ParameterParser. It
keeps a vector of parameters. Later, in ServerParser::inPacket, we call
ParameterParser::getParam to get the parameter at index 0. However it's
possible to send just the string " " which will result in the ParameterParser
vector having zero entries. Then this zero-indexing will go out of bounds.
This is unfortunately a DoS at best since the retrieved parameter is just
compared against "clientinitiv", which isn't terribly useful.
--- vuln 10: double clientinit DoS ---
If we run the "clientinit" command twice, the server crashes with a failed
assertion.
Q&A:
Q: What can normal users do?
A: Wait for the Teamspeak developers to patch the vulnerabilities, then download
a new version of the server. Exploiting these vulns to get RCE is quite
tricky and it's unlikely that anyone will manage to create a working
exploit before a patch is released.
Q: How did you find these?
A: Lots of staring at disassembly.
Q: Why not do coordinated disclosure?
A: The Teamspeak developers censor their forums and sweep vulnerabilities
under the rug as "crashes". I am not comfortable with that. Furthermore I
fear legal action from them.
Q: Does this mean that Teamspeak is totally insecure? Should users move to
other VoIP software?
A: Any complex piece of software has vulnerabilities. I would guess that
similar things could be found in Mumble/Ventrilo/etc., if enough time were
devoted to it.
Advisory by:
ff214370685e536b9ee021c7ff6b7680bfbe6008bc29f87511b6b90256043536