Лаборатория [DEF CON Finals 2012] torqux service writeup

, 06 August 2012

The DEF CON 20 is over.

This year SiBears were honored to participate in the final CTF round. Unfortunately, only 3 guys solved "US Visa 500" task and came to Las Vegas, so we were the smallest team in the CTF area. The DEFCON CTF Finals was a classical attack-and-defense game with services and regularly updated flags. (yep, they did have a Blackjack:)

The only non-binary service was torqux.

We are given 2 .pyc files. After decompyling, they look like this: file1 and file2.

A couple of minutes later we see that this service is an IRC bot that connects to the local IRC server:

def get_server():
    ip = get_ip('em1', AF_INET6)
    good = ':'.join(ip.split(':')[:-1] + ['7'])
    return good
class wutwut():
    def __init__(self, configFile):
        self._configFile = configFile
        self._ip = configFile
bot = wutwut(get_server())

As we can see, bot can load its configuration from the configfile with the same name as server IP, but by default there is no config file on the disk.

When the initialization is done, torqux bot joins the #eip channel (by default) and starts listening for commands from users. Main bot functionality is implemented in stuff.py file. It is being imported during the init phase and re-imported every time when _reloadModules() function is called.

The first thing we noticed was that messages command allows reading and writing arbitrary files on the bot server. This is possible because all messages are saved in files named %username%, so we can get "messages" from any file, as far as the IRC server allows us to use its name as nick. Moreover, we can append data to any file by providing its path instead of the recipient name.

Next (vulner)ability was that there is no mapping between bot commands and handling functions. Therefore, we can force the bot to call any function from stuff.py (or other imported modules from config), including _addit() (it adds new module to the config) and _makeit() (changes the privilege level of the current user).

def _addit(bot, name, reason, extra):
def _makeit(bot, user, target, argument):
    bot.config['admins'][user] = int(argument)
    print repr(bot.config)

This leads us to a pretty RCE vector:

  1. use !messages command to create a new Python module in the service' directory. Note that we can write it line by line, "sending" several messages.
  2. use !_addit command to add our new module to the list of imported modules
  3. !_makeit 0 to become admin (ordinary users cannot call _reloadModules)
  4. !reload modules to import the evil module
  5. !somefunction will execute somefunction() from evil module.

In conclusion, here is a little PoC script for the vector above.

Thanks for attention, be positive, and remember - CTF is friendship! (c)