Writeup – Opabina 1,2,3 (Google)

Late on Sunday night, I attempted the Opabina series of challenges in Google’s CTF. These challenges relied on the Protocol Buffers stack. Long story short, a Protocol Buffer is a language-independent definition of a given data structure (which can be reasonably complex with some in-built logic), and Google provides a tool to create “wrapper code” around whichever language you’re using.

The challenge started reasonably simply: we were man-in-the-middling a connection, and the MitM tool we used communicated via a variant of protocol buffers (32-bit little endian integer size, protocolbuffer.SerializeToString()). The actual Protocol Buffer definition was as follows:

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 first step was to compile the provided protocol buffer file, using “protoc”. I compiled to Python output, to assist in the rest of the challenge.

Opabina 1: Token Fetch

The first challenge was relatively simple: upon connecting to the challenge endpoint using an SSL-wrapped socket, you were presented with a Protocol Buffer, representing an HTTP request to /not-token. To win, you simply replied with the same request, except to /token:

huzzah!

huzzah!

There’s two gotcha’s to this:

  • Firstly, the server sends you data in the format (little_endian_length,marshalled_data). If you forget about the length field, and de-serialize the entire data stream: it *may* work, and it *may* leave you with an object that looks correct, but re-serializing the data will leave you with a broken object.
  • You couldn’t just send a serialized reply object: you had to send a serialized Exchange() object with a reply property/sub-object.

At this point (1am Monday morning), I thought the CTF was finished so I played a bit of Dark Souls 3. A few Yhorm the Giant kills later, and I realized the CTF finished at 3am, so I got back to work. Onwards!

Opabina 2: Downgrade

The downgrade attack was a lot more complex – fetching /protected/secret provoked a WWW-Authorize response, asking for Digest authentication. Unfortunately, we had no idea of the username and password.

Initially, I tried to guess the password, based on the realm (“In the realm of the hackers”) – I tried “electron:phoenix” and a few other variations, but this proved fruitless.

My second approach was to downgrade the user’s authentication to Basic, allowing us to retrieve the user’s password, as follows:

Client: "I'd like /protected/secret"

Server: "Authenticate, Digest please" --- MITM ---> "Authenticate, WWW-Basic please"

Client: "I'd like /protected/secret,Authentication: Basic _base64_" ---- MITM ----> "Authenticate, Digest XYZ"

A quick test later, and we are rewarded with a set of credentials:

why hello there google.ctf

why hello there google.ctf

The next part of this is setting up the Digest authentication, but fortunately, we can get some help from Github:

success!

success!

Opabina 3: Redirect

Opabina 3 was a real piece of work – with no real clue aside from the title, and the directive to retrieve “/protected/secret”, I set to work:

initial request from the user

initial request from the user

Simply modifying the initial request to “/protected/secret” doesn’t work: the client is challenged for Digest authentication, and happily provides it, for “/protected/not-secret”, resulting in an HTTP 401 Unauthorized status.

At this point, I tried a number of dead-ends from web application security – I tried a localhost referrer, bad Host headers and even things like /robots.txt. Eventually, I realized the correct approach was to trick the client in to legitimately requesting “/protected/secret”,

The key to this was to simply do nothing and let the user retrieve “/protected/not-secret”, and at that point, redirect the user to “/protected/secret” via an HTTP 302 message (i.e. MitMing the server response to replace the page with a redirect):

fuck yes

fuck yes

Of the Opabina challenges I solved, I found this one the most enjoyable, as this isn’t a type of challenge I had seen too often.

Source code is here: it’s easy enough to reconstruct the other attacks using the nice_send and nice_recv functions implemented. Note that the source code contains some test content from Opabina 2 / Downgrade: I got lazy and cut it off with sys.exit(0).

Great job Google at putting this challenge up and making me learn Protocol Buffers!

About Norman

Sometimes, I write code. Occasionally, it even works.
This entry was posted in Bards, Computers, Jesting and tagged , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s