Intigriti CTF 2024 - Cracking the Pizza Paradise Challenge
Recently, I had the chance to participate in a CTF, hosted by Intigriti. They had put in what I assume was an enormous amount of work in preparing the challenges, and several of their technical staff were available on a Friday night and through Saturday to answer my n00b questions, which I was extremely grateful for.
This is a write-up of the Pizza Paradise challenge (or “chall”, as Intigriti seem to call it). It was in the web hacking category, and I’ve chosen this challenge because a) we actually finished it, and b) it was exciting, being something of a last-minute buzzer beater.
First, Recon
As with any web hacking, time spent on recon is seldom wasted. The initial brief just says that there’s something weird going on with the website, and it’s got a point. None of the nav links work, the anchor href’s are all empty fragments, and most egregiously of all you can’t order a pizza. It may seem we are struggling for attack surface a bit, but there are some quick ways to find out more about the structure of a web application.
Sitemap
Being web developers and not hackers by trade, we might not have blasted through the challenges as easily as winners The Few Chosen, but we felt a little more comfortable on the web hacking challenge than some of the others. The first thing we looked for to find out more about what kind or URL’s were available was the sitemap.
A sitemap is an XML file that provides a list of publicly accessible URL’s on a website. (In theory, the list is
exhaustive, but in reality that is rarely the case.) This blog has one, and it is there to let the Googlebot know about
the latest banging content that’s just been published. By visiting the example.com/sitemap.xml
file, the Googlebot can
quickly find out if there is a new post their search engine wouldn’t be the same without.
Sadly, Pizza Paradise didn’t provide a sitemap.xml
file, so there was no help there.
robots.txt
The robots.txt
file of a website is another file that’s there mainly for search engines. In this case, the file
contains instructions on which bits of the site the crawler actually has permission to visit. It can be a common
practice to use example.com/robots.txt
to prevent certain pages from being indexed, and this often includes admin-only
areas of a web application. Here is the robots.txt
file of Pizza Paradise:
User-agent: *
Disallow: /secret_172346606e1d24062e891d537e917a90.html
Disallow: /assets/
The User-agent
component is a wildcard here, but it could be used to give different instructions to Google, to Bing,
and to an in-house tool doing something to do with SEO or smoke testing. The Disallow
directives tell crawlers to
leave those areas of the site alone, and refrain from visiting or indexing them.
The assets/
directory was already familiar to me as the location of pictures of pizza, but there is an obvious secret
page there as well.
Login Functionality
The secret page turned out to be a login screen. The web application really belongs to the government, and Pizza Paradise was just a front, explaining why you can’t actually buy a pizza.
Risking getting vanned by the feds, we proceeded with caution.
Timing is Everything
Being a team of web developers, we found certain things more challenging than others, but the nuances of web technologies were familiar in places. While guessing a username and password, we noticed the response was instantaneous. You don’t have to be James Kettle to know that the speed at which the authentication error message is being served means that the login is being done entirely client-side. This is certainly unusual, so we investigated further.
Client-Side Authentication
Right-clicking on the feds’ login page and choosing Inspect
, we opened Chrome dev tools.
Dev tools like this are every web developer’s best friend, and historically grew out of a Firefox plugin called Firebug. Active from around 2006, Firebug was at one time the only way to work out what on Earth was going on when your AJAX or CSS were doing something weird. Much of the functionality was mirrored in Firefox and later Chrome, and Firebug eventually retired in 2017, after an 11 year shift of being the only way anyone ever made anything on the internet actually work.
Chrome’s modern dev tools should become just as familiar to anyone interested in web development or web hacking.
Navigating to the Sources
tab, we can see from this screenshot that, once again, the simplicity of Pizza Paradise is
guiding us in the right direction. One javascript file and it’s got six lines of code. If only our day jobs should be so
simple.
const validUsername = "agent_1337";
const validPasswordHash = "91a915b6bdcfb47045859288a9e2bd651af246f07a083f11958550056bed8eac";
function getCredentials() {
return {
username: validUsername,
passwordHash: validPasswordHash,
};
}
Going back to the Elements
tab, we can see that there is an additional <script>
tag in the HTML <head>
:
<script>
function hashPassword(password) {
return CryptoJS.SHA256(password).toString();
}
function validate() {
const username = document.getElementById("username").value;
const password = document.getElementById("password").value;
const credentials = getCredentials();
const passwordHash = hashPassword(password);
if (
username === credentials.username &&
passwordHash === credentials.passwordHash
) {
return true;
} else {
alert("Invalid credentials!");
return false;
}
}
</script>
Making a Hash of It
Intro to Password Hashing
Password hashing simply means scrambling a password irreversibly. The effects are reproducible: compare a hashed password in your database to a hash of a login attempt, and you will know if the user entered the correct password.
The point of this exercise is to avoid having an unhashed password in your database. Storing users’ passwords without hashing is dangerous, as someone might break in and steal them. At least with a hashing algorithm applied, there is more work yet to be done by the hacker before the password is discovered.
As an aside, this tends to trip junior web developers up a bit. A common question that gets asked is, why not do the hashing client-side? Doesn’t that extend the security value of obfuscating the password so that it is just as useless captured in flight as it is if stolen from a database? Simply put, whatever the client (browser) sends to the webserver is the password. Having it be a hash of something else adds no value, so we never see password hashing client-side in real web applications.
Investigating the Hash
A glance at the javascript tells us that the username we need is agent_1337
, and the password hash is
91a915b6bdcfb47045859288a9e2bd651af246f07a083f11958550056bed8eac
. This hash was created using the SHA256 hashing
algorithm, so there is no realistic chance of brute-forcing the password by trying every possible string. We need a
better way.
Hashcat
Hashcat is a programme for cracking password hashes. We had set up an Ubuntu desktop with Nvidia graphics drivers and CUDA - and only bricked the OS completely twice - before the challenge. This enabled us to use not just the CPU, but the GPU, to crack hashed passwords. Hashing is a task that lends itself to high throughput, less latency, and massive parallelism, so GPU’s are perfect for computing lots of hashes in parallel.
The important thing to understand what Hashcat is doing is that it needs a wordlist. You can actually use it without
one. Ask it what string, when SHA256’ed, gives you 91a915b6bdcfb47045859288a9e2bd651af246f07a083f11958550056bed8eac
,
and it will sit there dutifully trying every one starting from "" until it finds the answer, some time between now and
the heat death of the universe.
With a wordlist, however, the odds are in Hashcat’s favour. Sitting there trying a dictionary of possible passwords and hashing each one, it will get you an answer in reasonable time. This, fundamentally, is why your password shouldn’t be the name of your dog. Hashcat can even be configured to try every password in its dictionary with random numbers at the end, and during a recent responsible disclosure, we found that 25% of password hashes involved in a data leak (don’t ask) could be broken in under a day using such methods.
Intermission
Unfortunately trying Hashcat with our favourite wordlist of the top 100,000 passwords got us nowhere, and we moved on to other challenges in the CTF, vowing to come back to the Pizza Paradise after we had, ironically, ordered a pizza. One dead end led to another, and no points were scored on any challenges for a while.
We Will Rock You
The most famous wordlist of all time is rockyou.txt
. RockYou was a company that made plugins for MySpace and Facebook
tools, active from 2005-2019. In 2009, the company got hacked, leaking 32 million unhashed user passwords. The 14
million unique passwords involved now ship with Kali Linux, and should arguably have been the first thing we tried.
Coming back to the task during a final sweep of half-finished challenges, the password popped with just 12 minutes
to go. It was intel420
.
Race Against the Clock
With only 12 minutes remaining, and facing the daunting prospect of finishing 223rd, we really needed the points. One of our two-man team had literally gone to bed at this point, and there was no way to know how many more steps there were to this challenge.
If you’ve never completed a hack with a time limit before, you absolutely have to try it.
Pics or It Isn’t Happening
The application I had logged on to was an image downloader. There were four pictures available to be downloaded, all espionage themed. We had seen CTF challenges involving image files before, including the infamous bin cat challenge.
exiftool
revealed nothing interesting. They are images. There weren’t any flags there.
strings
similarly failed to turn up any hidden flags or clues in the image.
Steganography hadn’t been checked, but I didn’t have a tool for that handy, and there wasn’t time to install one.
9 minutes.
Back on Track
This is a web hacking challenge. There is a web application. It’s reasonable to expect that the pictures of spies, recently downloaded, aren’t really the point.
The image downloader tool and it’s behaviour can be examined in the Chrome Inspect dev tools, on the Network
tab.
Clearing unwanted records and downloading a file, we see this:
The URL at the bottom says
https://pizzaparadise.ctf.intigriti.io/topsecret_a9aedc6c39f654e55275ad8e65e316b3.php?download=%2Fassets%2Fimages%2Ftopsecret4.png
.
Breaking that down, we see a really old-school approach to the PHP here - there is an actual source code file with a
.php
file extension corresponding to the endpoint. No modern frameworks with their front controllers or their
FastRoute libraries, just a simple PHP file. An elegant weapon, from a more civilised age.
The URL contains a single GET variable in the query string: download
. With /
characters url encoded, the path it
downloads is %2Fassets%2Fimages%2Ftopsecret4.png
, from the familiar assets/
directory, where we found the
javascript.
7 minutes.
Walk the Path
What happens if we change topsecret4.png
to ..%2F
? It turns out, something fascinating gets downloaded:
$ cat images
<br />
<b>Notice</b>: readfile(): Read of 8192 bytes failed with errno=21 Is a directory in <b>/var/www/html/topsecret_a9aedc6c39f654e55275ad8e65e316b3.php</b> on line <b>13</b><br />
The file, called images
contains a PHP error saying it can’t be downloaded because it is a folder.
This means that the application is vulnerable to path traversal, meaning that we can get it to download potentially any file from the server.
So the relevant directory structure, as far as we know, is:
/var/
- www/
- html/
- topsecret_a9aedc6c39f654e55275ad8e65e316b3.php
- assets/
- js/
- auth.js
- images/
- topsecret1.png
- topsecret2.png
- topsecret3.png
- topsecret4.png
There isn’t a flag
or flag.txt
file on the server anywhere I can get to.
4 minutes.
Whipping Up a Storm
It turns out, simply by visiting a modified URL, you can effect source code exfiltration of
topsecret_a9aedc6c39f654e55275ad8e65e316b3.php
.
PhpStorm is an integrated development environment for PHP developers. It’s built-in static analyser is the best around, but if your biggest codebase is no bigger than a few thousand lines, or if you want a tool for reading source code that works with every language, you don’t really need a PhpStorm licence. Use VSCode.
Opening the file in PhpStorm finally reveals the flag:
<?php
$flag = 'INTIGRITI{70p_53cr37_m15510n_c0mpl373}';
if (isset($_GET['download'])) {
$file = $_GET['download'];
if (strpos($file, '/assets/images/') === 0) {
$filePath = __DIR__ . '/' . $file;
if (file_exists($filePath)) {
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($filePath) . '"');
header('Content-Length: ' . filesize($filePath));
readfile($filePath);
exit();
} else {
die('File not found!');
}
} else {
die('File path not allowed!');
}
}
// ...
The flag is near the top, in an unused variable assignment.
Buzzer Beater
Our team submitted the flag for this challenge with just 3 minutes left on the clock, after 36 hours of hacking.
The Intigriti team did a wonderful job at this CTF. A lot of work went into preparing the challenges, and it shows. As if that weren’t enough, several of them gave up their weekend to administer the event and field our questions. If there is another CTF next year, we will definitely compete, and aim to climb higher in the rankings. (Joint 198th out of 1,061 this time!) Overall, it was a lot of fun, and it was worth it just to learn how all the tools work.
This is a topic for another blog post, perhaps at a later date, but the exercise has convinced one of us to start hacking on bug bounty programmes for real.