# Pickle Deserialization via __reduce__

Language: Python
Severity: Critical
CWE: CWE-502

## Source
9

## Flow
9-11-12

## Sink
12

## Vulnerable Code
```python
import pickle
import base64
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/iot/device/restore', methods=['POST'])
def restore_device_config():
    encoded_state = request.json.get('device_state')
    if encoded_state:
        serialized_config = base64.b64decode(encoded_state)
        device_obj = pickle.loads(serialized_config)
        device_obj.apply_settings()
        return jsonify({'status': 'Device configuration restored', 'device_id': device_obj.device_id})
    return jsonify({'error': 'No state provided'}), 400
```

## Explanation

The application accepts base64-encoded serialized data from an untrusted client request, decodes it, and passes it directly to pickle.loads() without any validation. Pickle deserialization of untrusted data allows arbitrary code execution because attackers can craft malicious pickle payloads using __reduce__ methods to execute arbitrary system commands. The source is the user-controlled input at line 9 (request.json.get), which flows through base64 decoding at line 11 and into the unsafe pickle.loads() call at line 12.

## Remediation

The fix replaces unsafe pickle deserialization with JSON deserialization, which cannot execute arbitrary code during parsing. Additionally, it adds input validation with a whitelist of allowed configuration fields, uses a safe DeviceConfig class instead of deserializing arbitrary objects, and optionally supports HMAC signature verification to ensure data integrity from trusted sources. Proper error handling for malformed input is also added.

## Secure Code
```python
import json
import base64
import hmac
import hashlib
from flask import Flask, request, jsonify

app = Flask(__name__)

# Secret key for HMAC signature verification (should be stored securely in production)
SIGNING_SECRET = b'your-secure-signing-secret-here'

# Whitelist of allowed device configuration fields
ALLOWED_CONFIG_FIELDS = {'device_id', 'network', 'sensors', 'firmware_version', 'polling_interval', 'thresholds', 'display', 'power_mode'}


class DeviceConfig:
    """Safe device configuration class that only accepts validated fields."""
    def __init__(self, config_dict):
        self.device_id = config_dict.get('device_id')
        self.settings = {}
        for key, value in config_dict.items():
            if key in ALLOWED_CONFIG_FIELDS:
                self.settings[key] = value

    def apply_settings(self):
        # Apply validated settings to the device
        pass


def verify_signature(data_bytes, signature):
    """Verify HMAC signature to ensure data integrity."""
    expected_sig = hmac.new(SIGNING_SECRET, data_bytes, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected_sig, signature)


def validate_config(config_dict):
    """Validate that the configuration dictionary contains only safe, expected fields and types."""
    if not isinstance(config_dict, dict):
        return False
    if 'device_id' not in config_dict:
        return False
    if not isinstance(config_dict.get('device_id'), str):
        return False
    for key in config_dict:
        if key not in ALLOWED_CONFIG_FIELDS:
            return False
    return True


@app.route('/iot/device/restore', methods=['POST'])
def restore_device_config():
    encoded_state = request.json.get('device_state')
    signature = request.json.get('signature')

    if not encoded_state:
        return jsonify({'error': 'No state provided'}), 400

    try:
        decoded_bytes = base64.b64decode(encoded_state)

        if signature:
            if not verify_signature(decoded_bytes, signature):
                return jsonify({'error': 'Invalid signature - data integrity check failed'}), 403

        config_dict = json.loads(decoded_bytes)

        if not validate_config(config_dict):
            return jsonify({'error': 'Invalid configuration format'}), 400

        device_obj = DeviceConfig(config_dict)
        device_obj.apply_settings()

        return jsonify({'status': 'Device configuration restored', 'device_id': device_obj.device_id})

    except (json.JSONDecodeError, UnicodeDecodeError):
        return jsonify({'error': 'Invalid device state encoding'}), 400
    except Exception:
        return jsonify({'error': 'Failed to restore device configuration'}), 500
```
