# Insecure Direct Object Reference via Predictable Session Token in HMAC-less Cookie Forgery

Language: Python
Severity: Critical
CWE: CWE-345

## Source
9

## Flow
9-11

## Sink
11

## Vulnerable Code
```python
import base64, json
from flask import Flask, request, jsonify
app = Flask(__name__)
user_profiles = {1: {'name': 'Alice', 'role': 'admin'}, 2: {'name': 'Bob', 'role': 'user'}}
def decode_iot_session(raw_cookie):
    try:
        decoded_payload = base64.b64decode(raw_cookie).decode('utf-8')
        session_obj = json.loads(decoded_payload)
        uid = session_obj.get('uid')
        return user_profiles.get(int(uid))
    except Exception:
        return None
@app.route('/api/iot/device-config')
def fetch_device_cfg():
    sess_tok = request.cookies.get('iot_sess')
    profile = decode_iot_session(sess_tok)
    if profile:
        return jsonify({'config': profile, 'status': 'ok'})
    return jsonify({'error': 'unauthorized'}), 401
```

## Explanation

The cookie value 'raw_cookie' is read from untrusted user-controlled input (line 6) and base64-decoded then JSON-parsed without any signature or HMAC verification. The extracted 'uid' value (line 8) is directly used to look up user profiles (line 9), allowing an attacker to forge any cookie by simply base64-encoding a JSON blob with an arbitrary uid (e.g., {'uid': 1}) to impersonate any user including admins.

## Remediation

The fix adds HMAC-SHA256 signature verification to the session cookie. The cookie now contains both a base64-encoded payload and a cryptographic signature computed using a server-side secret key. During decoding, the signature is recomputed and compared in constant time (via hmac.compare_digest) against the provided signature, ensuring that any forged or tampered cookie is rejected. A helper function create_iot_session is also provided so the server can issue properly signed cookies.

## Secure Code
```python
import base64, json, hmac, hashlib, os
from flask import Flask, request, jsonify

app = Flask(__name__)
app.config['SESSION_SECRET'] = os.environ.get('SESSION_SECRET', os.urandom(32).hex())

user_profiles = {1: {'name': 'Alice', 'role': 'admin'}, 2: {'name': 'Bob', 'role': 'user'}}

def _compute_hmac(payload_bytes):
    """Compute HMAC-SHA256 signature for the given payload."""
    secret = app.config['SESSION_SECRET'].encode('utf-8')
    return hmac.new(secret, payload_bytes, hashlib.sha256).hexdigest()

def create_iot_session(uid):
    """Create a signed session cookie value for the given uid."""
    payload = json.dumps({'uid': uid}).encode('utf-8')
    signature = _compute_hmac(payload)
    token_data = json.dumps({
        'payload': base64.b64encode(payload).decode('utf-8'),
        'sig': signature
    }).encode('utf-8')
    return base64.b64encode(token_data).decode('utf-8')

def decode_iot_session(raw_cookie):
    try:
        token_data = json.loads(base64.b64decode(raw_cookie).decode('utf-8'))
        payload_b64 = token_data.get('payload')
        provided_sig = token_data.get('sig')
        if not payload_b64 or not provided_sig:
            return None

        payload_bytes = base64.b64decode(payload_b64)
        expected_sig = _compute_hmac(payload_bytes)

        if not hmac.compare_digest(provided_sig, expected_sig):
            return None

        session_obj = json.loads(payload_bytes.decode('utf-8'))
        uid = session_obj.get('uid')
        if uid is None:
            return None

        uid_int = int(uid)
        return user_profiles.get(uid_int)
    except (ValueError, TypeError, json.JSONDecodeError, Exception):
        return None

@app.route('/api/iot/device-config')
def fetch_device_cfg():
    sess_tok = request.cookies.get('iot_sess')
    if not sess_tok:
        return jsonify({'error': 'unauthorized'}), 401
    profile = decode_iot_session(sess_tok)
    if profile:
        return jsonify({'config': profile, 'status': 'ok'})
    return jsonify({'error': 'unauthorized'}), 401
```
