{"title":"Insecure Direct Object Reference (IDOR)","language":"Python","severity":"Critical","cwe":"CWE-639","source_lines":[9],"flow_lines":[9,11],"sink_lines":[11],"vulnerable_code":"from flask import Flask, request, jsonify\nfrom pymongo import MongoClient\nfrom bson import ObjectId\napp = Flask(__name__)\ndb = MongoClient('mongodb://localhost:27017/')['iot_platform']\n@app.route('/api/device/telemetry', methods=['GET'])\ndef fetch_device_telemetry():\n    dev_id = request.args.get('device_id')\n    rec = db.telemetry.find_one({'_id': ObjectId(dev_id)})\n    if not rec:\n        return jsonify({'err': 'device not found'}), 404\n    return jsonify({'owner': str(rec['owner_id']), 'readings': rec['sensor_data'], 'secret_key': rec['api_secret']}), 200","explanation":"The endpoint accepts a user-supplied 'device_id' query parameter and directly uses it to query MongoDB without any authorization check verifying that the requesting user owns or has permission to access the specified device. Any authenticated (or unauthenticated) user can enumerate valid MongoDB ObjectIds and retrieve any device's telemetry, owner information, and API secret keys belonging to other users.","remediation":"The fix adds three layers of protection: (1) an authentication decorator (require_auth) ensures only authenticated users can reach the endpoint and populates g.current_user_id; (2) the MongoDB query is scoped by both _id and owner_id, so a user can only retrieve telemetry for devices they own, eliminating the IDOR; (3) the sensitive api_secret field is no longer included in the response, and the ObjectId conversion is wrapped in a try/except to handle malformed input gracefully.","secure_code":"from flask import Flask, request, jsonify, g\nfrom pymongo import MongoClient\nfrom bson import ObjectId\nfrom bson.errors import InvalidId\nfrom functools import wraps\n\napp = Flask(__name__)\ndb = MongoClient('mongodb://localhost:27017/')['iot_platform']\n\n\ndef require_auth(f):\n    \"\"\"Decorator that authenticates the request and populates g.current_user_id.\"\"\"\n    @wraps(f)\n    def decorated(*args, **kwargs):\n        # --- Replace the following stub with your real authentication logic ---\n        # e.g. validate a JWT / session token from the Authorization header\n        auth_header = request.headers.get('Authorization', '')\n        if not auth_header:\n            return jsonify({'err': 'authentication required'}), 401\n\n        user_id = _verify_token(auth_header)  # must return a valid user id or None\n        if user_id is None:\n            return jsonify({'err': 'invalid or expired token'}), 401\n\n        g.current_user_id = user_id\n        return f(*args, **kwargs)\n    return decorated\n\n\ndef _verify_token(auth_header: str):\n    \"\"\"Stub – implement real token verification (JWT, OAuth, etc.).\"\"\"\n    # Return the authenticated user's ID (as a string) or None on failure.\n    # Example: return decode_jwt(auth_header.split(' ')[1])['sub']\n    return None\n\n\n@app.route('/api/device/telemetry', methods=['GET'])\n@require_auth\ndef fetch_device_telemetry():\n    dev_id = request.args.get('device_id')\n    if not dev_id:\n        return jsonify({'err': 'device_id is required'}), 400\n\n    # Safely convert the supplied id; reject malformed values.\n    try:\n        device_oid = ObjectId(dev_id)\n    except (InvalidId, TypeError):\n        return jsonify({'err': 'invalid device_id format'}), 400\n\n    # Query scoped to the authenticated user's ownership — prevents IDOR.\n    rec = db.telemetry.find_one({\n        '_id': device_oid,\n        'owner_id': g.current_user_id\n    })\n\n    if not rec:\n        # Intentionally vague to avoid leaking whether the id exists for another owner.\n        return jsonify({'err': 'device not found'}), 404\n\n    # Do NOT expose sensitive credentials such as api_secret to the client.\n    return jsonify({\n        'owner': str(rec['owner_id']),\n        'readings': rec['sensor_data']\n    }), 200"}