# Insecure Direct Object Reference via Predictable UUID Generation

Language: Python
Severity: Critical
CWE: CWE-330

## Source
7

## Flow
7-8-13-17

## Sink
17

## Vulnerable Code
```python
import random
import time
from flask import Flask, request, jsonify
app = Flask(__name__)
patient_records = {}
def gen_patient_token(seed_val=None):
    random.seed(seed_val or int(time.time()))
    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))
    return tok
@app.route('/api/ehr/register', methods=['POST'])
def register_patient():
    pname = request.json.get('name')
    rec_id = gen_patient_token()
    patient_records[rec_id] = {'name': pname, 'diagnosis': request.json.get('diagnosis')}
    return jsonify({'record_id': rec_id})
@app.route('/api/ehr/fetch', methods=['GET'])
def fetch_record():
    rid = request.args.get('record_id')
    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
```python
import uuid
import secrets
from flask import Flask, request, jsonify, session
from functools import wraps

app = Flask(__name__)
app.secret_key = secrets.token_hex(32)

patient_records = {}
user_record_access = {}  # Maps user_id -> set of record_ids they can access


def gen_patient_token():
    return str(uuid.uuid4())


def require_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        user_id = session.get('user_id')
        if not user_id:
            return jsonify({'error': 'Authentication required'}), 401
        return f(*args, **kwargs)
    return decorated


@app.route('/api/ehr/register', methods=['POST'])
@require_auth
def register_patient():
    pname = request.json.get('name')
    rec_id = gen_patient_token()
    patient_records[rec_id] = {'name': pname, 'diagnosis': request.json.get('diagnosis')}
    # Grant the registering user access to this record
    user_id = session['user_id']
    if user_id not in user_record_access:
        user_record_access[user_id] = set()
    user_record_access[user_id].add(rec_id)
    return jsonify({'record_id': rec_id})


@app.route('/api/ehr/fetch', methods=['GET'])
@require_auth
def fetch_record():
    rid = request.args.get('record_id')
    user_id = session['user_id']
    # Authorization check: ensure the user has access to this specific record
    if user_id not in user_record_access or rid not in user_record_access[user_id]:
        return jsonify({'error': 'Access denied'}), 403
    record = patient_records.get(rid)
    if not record:
        return jsonify({'error': 'Record not found'}), 404
    return jsonify(record)
```
