Michael Lohr
Michi's Blog

Michi's Blog

Hacking Google CTF - Episode 3

Hacking Google CTF - Episode 3

Michael Lohr's photo
Michael Lohr
·Nov 12, 2022·

5 min read

Subscribe to my newsletter and never miss my upcoming articles

This is a write-up about how I solved the third episode of the H4CK1NG GOOGL3 security CTF. If you didn't read my post about the first episode, I highly recommend you to check it out, since it introduces the concept of a CTF.

Challenge 01: OAuth 2.0

Hints:

  • Have a thorough look at the challenge intro video
  • Credentials might be accidentally left on the system somewhere

The first challenge gives one command and the hint that a key should be found and RFC 6749 (which is about Oauth) should be put to use.

The command opens a TCP connection using socat, which presents a prompt asking for a password: password challenge

This one took me a while to figure out. Each episode comes with an introductory video. Someone on the Hacking Google CTF gave a hint that the password is hidden in this video. It took us a while, but eventually we found it:

hidden password

Entering this password reveals a shell environment. Seems like it was the development environment of some developer creating a backup script for some documents:

shell like environment

The backup.py script reveals the ID of the document the developer tried to back up, but the method to get the credentials was not finished yet.

Okay, seems like we have the document, containing the flag? Now we probably have to somehow get the Oauth credentials to access that file. Luckily enough, we can find the credentials in .config/gcloud/legacy_credentials/backup-tool@project-multivision.iam.gserviceaccount.com/adc.json, which contains a client id, client email, and private key.

Looking at the documentation for the Google Api's OAuth 2 service, we now have to construct an RS256 JWT token with the following content and POST that to oauth2.googleapis.com/token:

{
   "iss":"backup-tool@project-multivision.iam.gserviceaccount.com",
   "scope":"https://www.googleapis.com/auth/documents.readonly",
   "aud":"https://oauth2.googleapis.com/token",
   "exp":1664813638,
   "iat":1664810038
}

By doing that we receive an access token that is valid for one hour to access that file. And of course, that file contains the flag!

Challenge 02: The Maze

Hints:

  • Python is not made for secure/jailed environments
  • A terminal might not be the best choice for this challenge

ASCII maze

Again, we are presented with a socat command, but this time with an interactive game. The player can move around, pick up stuff like keys to unlock doors, energy boosters (energy gets lost by walking), and traps to kill enemies. After playing the game for a while, it seems rather hard and not going anywhere.

The instructions for this challenge just mention that you have to cheat somehow. After some brainstorming and playing with the thought of writing a bot to solve the game, I remembered the most famous cheat code: The Konami Code. And indeed, entering the key sequence of that cheat opens up a shell-like environment.

Some toying around with that shell reveals that it is a really limited/jailed Python3 shell. This challenge took me a while, and I worked with other CTF participants to solve it. After some research, we found this article, which explains how a Python jail would work and how it can be escaped. However, our shell was even more limited, not persisting the session after each command, seemingly printing only one line of the result and having a character limit for the input.

First, we wanted to see, which classes are loaded. Because of the given limitation, we would have to manually print each entry of the subclasses array like so:

print(().class.bases[0].subclasses()[123]

To automate this, my friend wrote a Rust script that automatically connected, entered the Konami code, and executed the Python command. Now we had a list of classes, which contained some interesting entries. Most importantly we spotted the "builtinImporter" object at index 84. This allowed us to actually load modules like this:

().class.bases[0].subclasses()[84].load_module('os')

We eventually also found a way to persist our intermediate results between our inputs by creating new types and storing information in them:

# create new subclass
c=str.__base__.__subclasses__();c[0]("a",(),{"b":c[84].load_module})
# call it, like so
str.__base__.__subclasses__()[274]("os")

# which allows us to execute commands on the system
c=str.__base__.__subclasses__()[274].b("os");c.system("ls")
# which lists a 'flag' file

That's it, right? Well, not quite. We could not cat the file and print the output in our terminals because they interpreted some ANSI codes that hid the output. But thanks to our Rust program, we could actually see the raw output and find the flag there.

Challenge 03: Corgi

Hints:

  • You don't have to crack the encryption.
  • Maybe there was some useful code left from development

This challenge came with a QR code and an .apk file, which is an Android application. I found this online decompiler tool, which I could use to look at the decompiled source code.

The app seems to allow scanning QR codes (using Google Lens) to enable app content with some encryption magic. After making sure that there is nothing weird going on with that app, I installed it on my phone. Normally I would never install such files on my Phone from untrusted sources, but I haven't any experience with Android development and did not want to search for a good emulator that can cope with Google services. Google wouldn't install a virus on my phone - I hope at least.

Scanning the QR code with any reader app, will lead to this URL: https://corgis-web.h4ck.ctfcompetition.com/aHR0cHM6Ly9jb3JnaXMtd2ViLmg0Y2suY3RmY29tcGV0aXRpb24uY29tL2NvcmdpP0RPQ0lEPWZsYWcmX21hYz1kZWQwOWZmMTUyOGYyOTgwMGIxZTczM2U2MjA4ZWEzNjI2NjZiOWVlYjVmNDBjMjY0ZmM1ZmIxOWRhYTM2OTM5

Everything behind the / can be decoded using base64 and will result in another URL: https://corgis-web.h4ck.ctfcompetition.com/corgi?DOCID=flag&_mac=ded09ff1528f29800b1e733e6208ea362666b9eeb5f40c264fc5fb19daa36939

This URL is parsed by the app and is used to unlock content. However, this only works if the client is "subscribed" which is testing using the _mac value and some HMAC encryption, whose secrets can be found in the strings.xml file. However, the decompiled code is hard to read, and before trying to crack the encryption I wanted to have a look at the scanning logic.

The QrCodesKt.java has some debug logic that was left in the code. Specifically, when /debug and #force_subscribed is in the URL, we are "force subscribed" skipping the subscription check at all. Generating a QR code with its URL pointing to https://corgis-web.h4ck.ctfcompetition.com/debug/aHR0cHM6Ly9jb3JnaXMtd2ViLmg0Y2suY3RmY29tcGV0aXRpb24uY29tL2NvcmdpP0RPQ0lEPWZsYWcmX21hYz1kZWQwOWZmMTUyOGYyOTgwMGIxZTczM2U2MjA4ZWEzNjI2NjZiOWVlYjVmNDBjMjY0ZmM1ZmIxOWRhYTM2OTM5#force_subscribed and scanning it will reveal the flag in the app.

Conclusion

I quite liked reverse engineering the QR code logic of the third challenge. The second challenge took most of the time and nerves but was pretty rewarding. You can read my write-up for the next episode over here.

Episode Overview:

 
Share this