{"title":"Insecure Direct Object Reference via Mass Assignment","language":"Python","severity":"High","cwe":"CWE-915","source_lines":[5],"flow_lines":[5,6,7,8],"sink_lines":[7],"vulnerable_code":"from flask import request, jsonify\nfrom models import db, DeviceProfile\n@app.route('/iot/device/<int:dev_id>/config', methods=['PUT'])\ndef patch_device_cfg(dev_id):\n    payload = request.get_json()\n    rec = DeviceProfile.query.filter_by(id=dev_id).first_or_404()\n    for attr, val in payload.items():\n        setattr(rec, attr, val)\n    db.session.commit()\n    return jsonify({'status': 'updated', 'device': rec.to_dict()}), 200","explanation":"The endpoint accepts arbitrary JSON input via `request.get_json()` and blindly iterates over all keys using `setattr(rec, attr, val)`, allowing any client-supplied field name to overwrite any attribute on the DeviceProfile ORM object. This includes sensitive privileged fields such as `owner_id`, `tenant_id`, `is_admin`, or `firmware_locked` that should never be user-controllable. There is no allowlist or field validation to restrict which attributes can be modified.","remediation":"The fix introduces a strict allowlist (`ALLOWED_CONFIG_FIELDS`) of fields that clients are permitted to modify. Before any attribute is written, the handler validates the incoming JSON payload and rejects requests containing keys outside the allowlist with a 403 response. The iteration then only processes keys that exist in both the allowlist and the payload, preventing attackers from overwriting privileged fields like `owner_id`, `is_admin`, `tenant_id`, or `firmware_locked`.","secure_code":"from flask import request, jsonify\nfrom models import db, DeviceProfile\n\nALLOWED_CONFIG_FIELDS = frozenset([\n    'device_name',\n    'firmware_version',\n    'sampling_interval',\n    'reporting_endpoint',\n    'power_mode',\n    'wifi_ssid',\n    'wifi_password',\n    'timezone',\n    'led_enabled',\n])\n\n@app.route('/iot/device/<int:dev_id>/config', methods=['PUT'])\ndef patch_device_cfg(dev_id):\n    payload = request.get_json()\n    if not payload or not isinstance(payload, dict):\n        return jsonify({'error': 'Invalid or missing JSON body'}), 400\n\n    # Reject any keys that are not in the explicit allowlist\n    disallowed_keys = set(payload.keys()) - ALLOWED_CONFIG_FIELDS\n    if disallowed_keys:\n        return jsonify({\n            'error': 'Modification of the following fields is not permitted',\n            'fields': sorted(disallowed_keys)\n        }), 403\n\n    rec = DeviceProfile.query.filter_by(id=dev_id).first_or_404()\n\n    for attr in ALLOWED_CONFIG_FIELDS:\n        if attr in payload:\n            setattr(rec, attr, payload[attr])\n\n    db.session.commit()\n    return jsonify({'status': 'updated', 'device': rec.to_dict()}), 200"}