Containment Forever

Web [266 pts]

When we first visit the web page, we don’t get much information about the server or client. No cookies, no hidden directories, or anything. Therefore, to work with what we have, we navigate to the tabs given.

Main page:

Looking at the flag page, we see an arrangement that looks like a document used on a NoSQL database, like MongoDB.

The ObjectId is “not indexed at the moment”, but in the “Confinement” tab, it is indexed and all the information seems there and accessible.

Clicking on one of the links leads us to a page with the following url: /item/objectId:

So in MongoDB, ObjectId’s are calculated by a 4-byte timestamp value, 5-byte random value, and 3-byte incremental counter.


So knowing this, in the flag page, we are only given the timestamp. There is a way to retrieve an ObjectId if you have a timestamp. Using this link, we can use the given timestamp to get an ObjectId.


We notice that the middle 5-bytes are all the same, so this must be the system Id being used for all objects. Therefore, the last piece is to find the incremental counter to get a valid objectId for our flag.

We only need to do a small brute-force on the last digit, using the new ObjectId calculated from the given timestamp and the same SystemId used for all objects.

Doing so, we are able to retrieve the first and second flag by following this process:


Web [70 pts]

This challenge, as hinted by its name, involves an XXE attack. The goal, as described in the challenge, is to find the flag.txt file in the root directory of the PHP server.

Resource: XXE docs

Step 1

When we first visit the site, our landing page looks like this:

We assume that it takes an xml payload eventually, but when we test LFI on the query, we get this result:

(This is an error page, named error.xml)

Step 2

After messing around some more, we see that the PHP function calls loadXML() and expects a string: Resource: loadxml() docs

Step 3

We now attempt to try RFI. First, we check to see if their server visits our “website.” We test this with a webhooks site from

From these results, it looks like they do visit our webhook site. The only thing now is to find a way to include our xml payload that will be available in the url. My first instinct was to find a file upload site that can be shared (filebin).

Using the XML payload:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
  <!ELEMENT foo ANY >
  <!ENTITY xxe SYSTEM "file:///flag.txt" >]>

We can upload this using filebin and include the entire website in the query, giving us our flag:


Web [70 pts]

We first see a page containing text files, when we enter an input, we get directed to a location where there is a vulnerable search parameter.

Step 1

Originally, we can also do LFI + RCE for this but I did RFI + RCE instead, using Pastebin and the following php code:

Using the above code in a pastebin link gives us this result:

Step 2

We are interested in the directory “i_wonder_whats_in_here”, so our next payload will cat the contents of that directory:

Using this php payload in our pastebin and ?file= query, we look in page source and see the flag commented out:

Congenial Octo Couscous

Web [70 pts]

At first glance, we don’t see anything suspicious in our page or page source. Trying to access the endpoint /strategyguide.txt will return: ACCESS DENIED.

After messing around with the input, I did see that the username field is the one displaying our results. So I started experimenting with SSTI tests.

Trying this payload returns a bunch of information about the server. Exposing the backend technology (Jinja2/Flask) as well as the source code for this challenge.

Step 1

We look at the endpoint / and find this:

Making it more readable in a text editor:

Step 2

We see that there is a bunch of filters for SSTI, preventing various system commands and code execution.

After looking online at multiple ways to bypass this:

Resource: Jinja2 template injection filter bypasses

Resource: SSTI Jinja2 payloads
We came up with a payload to bypass these filters and to call subprocess to see if we can get information from the file directory:

Step 3

Using the above payload will result in this:

Now since we can run commands, we cat the text file for strategyguide.txt, giving us our flag:
{{request|attr(‘application’)|attr(‘__globals__’)|attr(‘__getitem__’)(‘__builtins__’)|attr(‘__getitem__’)(‘__import__’)(‘subprocess’)|attr(‘getoutput’)(‘cat strategyguide.txt’)}}

Moar Horse 4

Web [80 pts]

After visiting the site, we are given an option to buy a horse and ‘race’. The goal of this challenge is to beat the boss horse by having greater speed.

We are also given the source code for this challenge, showing how the logic is being done to calculate speed as well as information about our tokens.

It looks like we need to find a horse name with it’s md5 hash being greater than the boss horse. We also notice that we are given a JWT, when decoded, gives us:

An interesting piece they also give us is the public key used for verifying JWT tokens.

There is a well known vulnerability for RS256 JWT when given a public key. That is, we can convert RS256 -> HS256 and use the public key to generate a new signature and verify the token symmetrically.


Step 1

So trying out the steps in the above resource and changing our payload to give us more money + modifying our horse name:

We change our key format to ASCII hex and use openssl to sign our JWT (Header + Payload):

The picture above is our HMAC signature, we need to turn this back into ASCII hex to use in our token:

Using the new token with our modified payload, we do see that it indeed worked:

Step 2

To obtain the flag, we need to find a md5 hash that is greater than the boss horse. So I created a short python script to do this, using rockyou.txt:

After running our program, we see that a word has been found that is greater than the boss horse:

Step 3

We follow the same steps as Step 1, modifying the ‘horses’ name in the payload in the JWT to ‘panchito00’.

Doing so will give us the flag when we enter the modified JWT:



Web [80 pts]

The topic of this challenge is using a SHA256 hash to encode the password. The backend is PHP. There is a well known vulnerability in using this method, called type-juggling.

Resource: Typejuggling PHP

Step 1

Visiting the welcome page and attempting to log in will give us a generic error:

We try to retireve any information we can, so I used dirsearch in order to find hidden directories:

It appears that there is a .git directory, containing a bunch of information about the backend and probably the source code. I also found a file in one of the directories as well, whose purpose is to dump the entire database.

Step 2

Our next step is to try to dump the entire git repository to a local directory. In this challenge, I used git-dumper.

Looking at the repository, we see an index.php, containing logic for the login mechanism and how the password is being hashed and compared:

We also see that if there is a valid match, then we can obtain the flag:

The password is being hashed with SHA256 and then compared to a row in the database. So next, we explore the git objects and use git show to obtain information about the commit.

In the above picture, it seems that the owner ran the program that we saw earlier, dumping the entire database. Luckily, we only have to log in to one user in order to obtain the flag, so we have multiple choices here. I wanted to pick a user that had ‘0e’ and followed by all numbers to avoid further complications. In this case, it was the user: Andon1956.

Using this resource, we were able to find a ‘magic hash’ for 0e-allnumbers-: TyNOQHUS

Resource: Tyle Juggling Magic Hashes

Step 3

After entering the username: Andon1956 and password:TyNOQHUS, we are able to login due to PHP’s type juggling and weak comparison vulnerability.

Our flag is:

Admin Secrets

Web [100 pts]

When we first visit the page, we are given an area to create post and “report” to the Admin. Trying the classic XSS test,


gives us a popup window that we can base our target on.

Our next goal is to steal some cookies from the admin to retrieve more information. In this challenge, I used ngrok and a local php server containing the following code for steal.php:

Step 1

Using this code and ngrok, we attempt to retrieve the admins cookie shown in the following:

After pressing ‘report to admin’ , we see that we indeed retrieved something in our request:

We get an idea that the admin is visiting our site and giving us their information based on the referrer:

Step 2

To obtain the admin console, located in the page source, we have to do a similar process as Step 1. But, triggering our script before the page loads will cut off the rest of the page source after our script. So we have to wait until the page fully loads and then retrieve the entire HTML. I encoded this twice in base64 for readability.

Looking at our response, we see that we are given a twice base64 encoded string, once decoded using CyberChef, we were able to obtain the admin’s page source:

We observe that an ajax GET request is being made to the endpoint /admin_flag. When we try to access it using curl, it response “Only the administrator can access this endpoint.”

So in our next step, we have to get the administrator to visit that endpoint, send us the information, store the information, and redirect it back to us.

Step 3

The implementation of the logic in Step 2 will look something like this, using XHR:

After sending this payload, we notice that our response contains an extra step that prevents us from accessing the endpoint:

Step 4

There is a type of filtering that prevents script tags, quotes, and parenthesis.

Using the OWASP XSS Filter bypass link, we were able to encode and manipulate our payload to bypass this filter.

Resource: OWASP XSS filter bypassesOur modified payload will be:

Finally, using the above payload, we are able to see the flag in our response: