{"title":"Pickle Deserialization via __reduce__","language":"Python","severity":"Critical","cwe":"CWE-502","source_lines":[10],"flow_lines":[10,14,15],"sink_lines":[15],"vulnerable_code":"import pickle\nimport base64\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\n@app.route('/iot/device/restore', methods=['POST'])\ndef restore_device_config():\n    device_id = request.json.get('device_id')\n    encoded_config = request.json.get('config_snapshot')\n    \n    if not encoded_config:\n        return jsonify({'error': 'Missing configuration data'}), 400\n    \n    try:\n        config_bytes = base64.b64decode(encoded_config)\n        device_config = pickle.loads(config_bytes)\n        \n        apply_device_settings(device_id, device_config)\n        \n        return jsonify({\n            'status': 'success',\n            'device_id': device_id,\n            'message': 'Device configuration restored successfully'\n        }), 200\n    except Exception as e:\n        return jsonify({'error': str(e)}), 500\n\ndef apply_device_settings(dev_id, config):\n    print(f\"Applying settings to device {dev_id}: {config}\")","explanation":"The application accepts user-controlled base64-encoded data via the 'config_snapshot' parameter, decodes it, and directly deserializes it using pickle.loads() without validation. Pickle deserialization of untrusted data allows arbitrary code execution because attackers can craft malicious pickle payloads using __reduce__ methods to execute system commands.","remediation":"The fix replaces pickle.loads() with json.loads(), which cannot execute arbitrary code during deserialization. Additionally, a configuration validation layer with an allowlist of permitted keys and expected types ensures only legitimate device configuration data is accepted. An optional HMAC signature verification is added using the correct Python 3 hmac.HMAC() constructor to ensure configurations were created by the trusted system.","secure_code":"import json\nimport base64\nimport hmac\nimport hashlib\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\n# Secret key for HMAC signing - should be stored securely (e.g., environment variable)\nCONFIG_SIGNING_KEY = b'your-secret-signing-key-store-in-env'\n\n# Allowlist of valid configuration keys and their expected types\nALLOWED_CONFIG_KEYS = {\n    'network': dict,\n    'firmware_version': str,\n    'sensors': list,\n    'thresholds': dict,\n    'reporting_interval': (int, float),\n    'device_name': str,\n    'enabled': bool,\n    'wifi_ssid': str,\n    'wifi_channel': int,\n    'mqtt_broker': str,\n    'mqtt_port': int,\n}\n\n\ndef validate_config(config):\n    \"\"\"Validate that the configuration only contains allowed keys and types.\"\"\"\n    if not isinstance(config, dict):\n        raise ValueError(\"Configuration must be a dictionary\")\n    \n    for key, value in config.items():\n        if key not in ALLOWED_CONFIG_KEYS:\n            raise ValueError(f\"Unknown configuration key: {key}\")\n        expected_type = ALLOWED_CONFIG_KEYS[key]\n        if not isinstance(value, expected_type):\n            raise ValueError(f\"Invalid type for key '{key}': expected {expected_type}, got {type(value)}\")\n    \n    return True\n\n\ndef verify_config_signature(config_bytes, signature):\n    \"\"\"Verify the HMAC signature of the configuration data.\"\"\"\n    expected_sig = hmac.HMAC(CONFIG_SIGNING_KEY, config_bytes, hashlib.sha256).hexdigest()\n    return hmac.compare_digest(expected_sig, signature)\n\n\n@app.route('/iot/device/restore', methods=['POST'])\ndef restore_device_config():\n    device_id = request.json.get('device_id')\n    encoded_config = request.json.get('config_snapshot')\n    signature = request.json.get('signature')\n    \n    if not encoded_config:\n        return jsonify({'error': 'Missing configuration data'}), 400\n    \n    if not device_id or not isinstance(device_id, str):\n        return jsonify({'error': 'Invalid device_id'}), 400\n    \n    try:\n        config_bytes = base64.b64decode(encoded_config)\n        \n        # Optional: Verify HMAC signature if provided (for configs created by our system)\n        if signature:\n            if not verify_config_signature(config_bytes, signature):\n                return jsonify({'error': 'Invalid configuration signature'}), 403\n        \n        # Use JSON deserialization instead of pickle - safe from arbitrary code execution\n        device_config = json.loads(config_bytes.decode('utf-8'))\n        \n        # Validate the configuration structure and types\n        validate_config(device_config)\n        \n        apply_device_settings(device_id, device_config)\n        \n        return jsonify({\n            'status': 'success',\n            'device_id': device_id,\n            'message': 'Device configuration restored successfully'\n        }), 200\n    except (json.JSONDecodeError, UnicodeDecodeError):\n        return jsonify({'error': 'Invalid configuration format - must be valid JSON'}), 400\n    except ValueError as e:\n        return jsonify({'error': f'Configuration validation failed: {str(e)}'}), 400\n    except Exception as e:\n        return jsonify({'error': 'Internal server error'}), 500\n\n\n@app.route('/iot/device/backup', methods=['POST'])\ndef backup_device_config():\n    \"\"\"Create a signed backup of device configuration using safe JSON serialization.\"\"\"\n    device_id = request.json.get('device_id')\n    config = request.json.get('config')\n    \n    if not config or not isinstance(config, dict):\n        return jsonify({'error': 'Invalid configuration'}), 400\n    \n    try:\n        validate_config(config)\n        config_bytes = json.dumps(config, sort_keys=True).encode('utf-8')\n        encoded_config = base64.b64encode(config_bytes).decode('utf-8')\n        signature = hmac.HMAC(CONFIG_SIGNING_KEY, config_bytes, hashlib.sha256).hexdigest()\n        \n        return jsonify({\n            'device_id': device_id,\n            'config_snapshot': encoded_config,\n            'signature': signature\n        }), 200\n    except ValueError as e:\n        return jsonify({'error': str(e)}), 400\n\n\ndef apply_device_settings(dev_id, config):\n    print(f\"Applying settings to device {dev_id}: {config}\")"}