# Insecure Direct Object Reference (IDOR) via JWT Sub Claim Manipulation

Language: Python
Severity: Critical
CWE: CWE-639

## Source
6

## Flow
5-6-7-8

## Sink
8

## Vulnerable Code
```python
import jwt
from flask import request, jsonify
from db import fetch_medical_record
def get_patient_record():
    bearer = request.headers.get('Authorization', '').split(' ')[1]
    payload = jwt.decode(bearer, options={"verify_signature": False})
    pid = payload.get('sub')
    rec = fetch_medical_record(pid)
    if rec is None:
        return jsonify({'error': 'not found'}), 404
    return jsonify(rec), 200
```

## Explanation

The JWT token is decoded with 'verify_signature': False, meaning the server accepts any JWT without validating its cryptographic signature. An attacker can forge a JWT with an arbitrary 'sub' claim (any patient's ID) and the server will blindly trust it, passing the attacker-controlled 'pid' directly to fetch_medical_record() — a classic IDOR vulnerability enabling unauthorized access to other patients' medical records.

## Remediation

The fix enforces full JWT signature verification using a server-side secret key and a specified algorithm, so forged or tampered tokens are rejected. Additionally, an explicit authorization check is added so that even with a valid token, a user can only access their own medical record unless they hold a privileged role (e.g., doctor or admin), eliminating the IDOR vulnerability. Proper error handling for expired and invalid tokens is also included.

## Secure Code
```python
import jwt
from flask import request, jsonify
from db import fetch_medical_record
from config import JWT_SECRET_KEY, JWT_ALGORITHM

def get_patient_record():
    auth_header = request.headers.get('Authorization', '')
    if not auth_header.startswith('Bearer '):
        return jsonify({'error': 'missing or malformed authorization header'}), 401
    token = auth_header.split(' ', 1)[1]

    try:
        payload = jwt.decode(
            token,
            JWT_SECRET_KEY,
            algorithms=[JWT_ALGORITHM],
            options={"verify_signature": True}
        )
    except jwt.ExpiredSignatureError:
        return jsonify({'error': 'token expired'}), 401
    except jwt.InvalidTokenError:
        return jsonify({'error': 'invalid token'}), 401

    authenticated_user_id = payload.get('sub')
    if not authenticated_user_id:
        return jsonify({'error': 'invalid token claims'}), 401

    requested_pid = request.args.get('patient_id', authenticated_user_id)

    user_role = payload.get('role', 'patient')
    if requested_pid != authenticated_user_id and user_role not in ('doctor', 'admin'):
        return jsonify({'error': 'access denied'}), 403

    rec = fetch_medical_record(requested_pid)
    if rec is None:
        return jsonify({'error': 'not found'}), 404
    return jsonify(rec), 200
```
