{"title":"Insecure Direct Object Reference via Predictable UUID Generation","language":"Python","severity":"Critical","cwe":"CWE-330","source_lines":[7],"flow_lines":[7,8,13,17],"sink_lines":[17],"vulnerable_code":"import random\nimport time\nfrom flask import Flask, request, jsonify\napp = Flask(__name__)\npatient_records = {}\ndef gen_patient_token(seed_val=None):\n    random.seed(seed_val or int(time.time()))\n    tok = '%08x-%04x-%04x-%04x-%012x' % (random.randint(0,0xffffffff),random.randint(0,0xffff),random.randint(0,0xffff),random.randint(0,0xffff),random.randint(0,0xffffffffffff))\n    return tok\n@app.route('/api/ehr/register', methods=['POST'])\ndef register_patient():\n    pname = request.json.get('name')\n    rec_id = gen_patient_token()\n    patient_records[rec_id] = {'name': pname, 'diagnosis': request.json.get('diagnosis')}\n    return jsonify({'record_id': rec_id})\n@app.route('/api/ehr/fetch', methods=['GET'])\ndef fetch_record():\n    rid = request.args.get('record_id')\n    return jsonify(patient_records.get(rid, {}))","explanation":"The token generation function uses Python's `random` module seeded with the current Unix timestamp (`int(time.time())`), making the generated 'UUID-like' tokens fully predictable. An attacker who knows the approximate registration time can brute-force the seed within a narrow time window, reproduce the exact token sequence, and then call the unauthenticated `/api/ehr/fetch` endpoint with the guessed `record_id` to retrieve sensitive patient medical records — a classic IDOR via predictable identifier.","remediation":"The fix addresses all three layers of the vulnerability: (1) Replaced the predictable `random`-module-based token generation (seeded with `time.time()`) with `uuid.uuid4()`, which uses a cryptographically secure random source, making tokens unguessable. (2) Added authentication via a `require_auth` decorator that checks for a valid session before allowing access to any endpoint. (3) Added authorization checks on the `/api/ehr/fetch` endpoint to ensure that authenticated users can only access records they are explicitly permitted to view, preventing IDOR even if a token were somehow guessed.","secure_code":"import uuid\nimport secrets\nfrom flask import Flask, request, jsonify, session\nfrom functools import wraps\n\napp = Flask(__name__)\napp.secret_key = secrets.token_hex(32)\n\npatient_records = {}\nuser_record_access = {}  # Maps user_id -> set of record_ids they can access\n\n\ndef gen_patient_token():\n    return str(uuid.uuid4())\n\n\ndef require_auth(f):\n    @wraps(f)\n    def decorated(*args, **kwargs):\n        user_id = session.get('user_id')\n        if not user_id:\n            return jsonify({'error': 'Authentication required'}), 401\n        return f(*args, **kwargs)\n    return decorated\n\n\n@app.route('/api/ehr/register', methods=['POST'])\n@require_auth\ndef register_patient():\n    pname = request.json.get('name')\n    rec_id = gen_patient_token()\n    patient_records[rec_id] = {'name': pname, 'diagnosis': request.json.get('diagnosis')}\n    # Grant the registering user access to this record\n    user_id = session['user_id']\n    if user_id not in user_record_access:\n        user_record_access[user_id] = set()\n    user_record_access[user_id].add(rec_id)\n    return jsonify({'record_id': rec_id})\n\n\n@app.route('/api/ehr/fetch', methods=['GET'])\n@require_auth\ndef fetch_record():\n    rid = request.args.get('record_id')\n    user_id = session['user_id']\n    # Authorization check: ensure the user has access to this specific record\n    if user_id not in user_record_access or rid not in user_record_access[user_id]:\n        return jsonify({'error': 'Access denied'}), 403\n    record = patient_records.get(rid)\n    if not record:\n        return jsonify({'error': 'Record not found'}), 404\n    return jsonify(record)"}