{"title":"Unsafe `marshal.loads()` Deserialization","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    firmware_ver = device_params.get('firmware', '1.0')\n    return jsonify({'status': 'configured', 'device': device_id, 'version': firmware_ver})","explanation":"The application accepts user-controlled base64-encoded data via request.json.get('device_config'), decodes it, and directly deserializes it using marshal.loads(). The marshal module can execute arbitrary code during deserialization of malicious bytecode, allowing remote code execution.","remediation":"The fix replaces the dangerous marshal.loads() deserialization with json.loads(), which only parses data into safe primitive types (strings, numbers, lists, dicts) and cannot execute arbitrary code. Additionally, input validation, payload size limits, field whitelisting, and type checking are added to enforce a strict contract on acceptable configuration data.","secure_code":"import json\nimport base64\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\nALLOWED_FIELDS = {'device_id', 'firmware', 'network', 'polling_interval', 'enabled'}\nMAX_PAYLOAD_SIZE = 10240  # 10KB max config size\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({'error': 'Missing or invalid device_config'}), 400\n\n    try:\n        cfg_bytes = base64.b64decode(encoded_cfg)\n    except Exception:\n        return jsonify({'error': 'Invalid base64 encoding'}), 400\n\n    if len(cfg_bytes) > MAX_PAYLOAD_SIZE:\n        return jsonify({'error': 'Configuration payload too large'}), 400\n\n    try:\n        device_params = json.loads(cfg_bytes)\n    except (json.JSONDecodeError, ValueError):\n        return jsonify({'error': 'Invalid JSON configuration'}), 400\n\n    if not isinstance(device_params, dict):\n        return jsonify({'error': 'Configuration must be a JSON object'}), 400\n\n    # Only allow known configuration fields\n    filtered_params = {k: v for k, v in device_params.items() if k in ALLOWED_FIELDS}\n\n    device_id = filtered_params.get('device_id', 'unknown')\n    firmware_ver = filtered_params.get('firmware', '1.0')\n\n    # Validate field types\n    if not isinstance(device_id, str) or not isinstance(firmware_ver, str):\n        return jsonify({'error': 'Invalid field types in configuration'}), 400\n\n    return jsonify({'status': 'configured', 'device': device_id, 'version': firmware_ver})"}