{"title":"Insecure Direct Object Reference via Predictable Session Token in HMAC-less Cookie Forgery","language":"Python","severity":"Critical","cwe":"CWE-345","source_lines":[9],"flow_lines":[9,11],"sink_lines":[11],"vulnerable_code":"import base64, json\nfrom flask import Flask, request, jsonify\napp = Flask(__name__)\nuser_profiles = {1: {'name': 'Alice', 'role': 'admin'}, 2: {'name': 'Bob', 'role': 'user'}}\ndef decode_iot_session(raw_cookie):\n    try:\n        decoded_payload = base64.b64decode(raw_cookie).decode('utf-8')\n        session_obj = json.loads(decoded_payload)\n        uid = session_obj.get('uid')\n        return user_profiles.get(int(uid))\n    except Exception:\n        return None\n@app.route('/api/iot/device-config')\ndef fetch_device_cfg():\n    sess_tok = request.cookies.get('iot_sess')\n    profile = decode_iot_session(sess_tok)\n    if profile:\n        return jsonify({'config': profile, 'status': 'ok'})\n    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":"import base64, json, hmac, hashlib, os\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\napp.config['SESSION_SECRET'] = os.environ.get('SESSION_SECRET', os.urandom(32).hex())\n\nuser_profiles = {1: {'name': 'Alice', 'role': 'admin'}, 2: {'name': 'Bob', 'role': 'user'}}\n\ndef _compute_hmac(payload_bytes):\n    \"\"\"Compute HMAC-SHA256 signature for the given payload.\"\"\"\n    secret = app.config['SESSION_SECRET'].encode('utf-8')\n    return hmac.new(secret, payload_bytes, hashlib.sha256).hexdigest()\n\ndef create_iot_session(uid):\n    \"\"\"Create a signed session cookie value for the given uid.\"\"\"\n    payload = json.dumps({'uid': uid}).encode('utf-8')\n    signature = _compute_hmac(payload)\n    token_data = json.dumps({\n        'payload': base64.b64encode(payload).decode('utf-8'),\n        'sig': signature\n    }).encode('utf-8')\n    return base64.b64encode(token_data).decode('utf-8')\n\ndef decode_iot_session(raw_cookie):\n    try:\n        token_data = json.loads(base64.b64decode(raw_cookie).decode('utf-8'))\n        payload_b64 = token_data.get('payload')\n        provided_sig = token_data.get('sig')\n        if not payload_b64 or not provided_sig:\n            return None\n\n        payload_bytes = base64.b64decode(payload_b64)\n        expected_sig = _compute_hmac(payload_bytes)\n\n        if not hmac.compare_digest(provided_sig, expected_sig):\n            return None\n\n        session_obj = json.loads(payload_bytes.decode('utf-8'))\n        uid = session_obj.get('uid')\n        if uid is None:\n            return None\n\n        uid_int = int(uid)\n        return user_profiles.get(uid_int)\n    except (ValueError, TypeError, json.JSONDecodeError, Exception):\n        return None\n\n@app.route('/api/iot/device-config')\ndef fetch_device_cfg():\n    sess_tok = request.cookies.get('iot_sess')\n    if not sess_tok:\n        return jsonify({'error': 'unauthorized'}), 401\n    profile = decode_iot_session(sess_tok)\n    if profile:\n        return jsonify({'config': profile, 'status': 'ok'})\n    return jsonify({'error': 'unauthorized'}), 401"}