{"title":"Unsafe Object Deserialization via marshal.loads()","language":"Python","severity":"Critical","cwe":"CWE-502","source_lines":[9],"flow_lines":[9,10,11],"sink_lines":[11],"vulnerable_code":"import marshal\nimport base64\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\n@app.route('/iot/device/config', methods=['POST'])\ndef apply_device_configuration():\n    encoded_cfg = request.json.get('device_config')\n    cfg_bytes = base64.b64decode(encoded_cfg)\n    device_params = marshal.loads(cfg_bytes)\n    device_id = device_params.get('device_id', 'unknown')\n    settings = device_params.get('settings', {})\n    return jsonify({'status': 'configured', 'device': device_id, 'applied': settings})","explanation":"The application accepts untrusted base64-encoded data from device_config parameter, decodes it, and passes it directly to marshal.loads() without validation. The marshal module can execute arbitrary code during deserialization, allowing attackers to achieve Remote Code Execution by crafting malicious serialized payloads.","remediation":"The fix replaces the unsafe marshal.loads() deserialization with json.loads(), which only parses data into safe primitive types (dicts, lists, strings, numbers, booleans, null) and cannot execute arbitrary code. Additionally, input validation is added to verify the structure of the parsed data, and settings keys are filtered against an allowlist to enforce expected configuration parameters.","secure_code":"import json\nimport base64\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\nALLOWED_SETTINGS_KEYS = {'brightness', 'volume', 'wifi_ssid', 'wifi_password', 'firmware_version', 'update_interval', 'timezone', 'language'}\n\n@app.route('/iot/device/config', methods=['POST'])\ndef apply_device_configuration():\n    encoded_cfg = request.json.get('device_config')\n    if not encoded_cfg or not isinstance(encoded_cfg, str):\n        return jsonify({'status': 'error', 'message': 'Invalid or missing device_config'}), 400\n\n    try:\n        cfg_bytes = base64.b64decode(encoded_cfg)\n        device_params = json.loads(cfg_bytes)\n    except (base64.binascii.Error, json.JSONDecodeError, UnicodeDecodeError) as e:\n        return jsonify({'status': 'error', 'message': 'Invalid configuration format'}), 400\n\n    if not isinstance(device_params, dict):\n        return jsonify({'status': 'error', 'message': 'Configuration must be a JSON object'}), 400\n\n    device_id = device_params.get('device_id', 'unknown')\n    if not isinstance(device_id, str) or len(device_id) > 128:\n        return jsonify({'status': 'error', 'message': 'Invalid device_id'}), 400\n\n    settings = device_params.get('settings', {})\n    if not isinstance(settings, dict):\n        return jsonify({'status': 'error', 'message': 'Settings must be a JSON object'}), 400\n\n    validated_settings = {k: v for k, v in settings.items() if k in ALLOWED_SETTINGS_KEYS and isinstance(v, (str, int, float, bool))}\n\n    return jsonify({'status': 'configured', 'device': device_id, 'applied': validated_settings})"}