# Pickle Deserialization via __reduce__

Language: Python
Severity: Critical
CWE: CWE-502

## Source
10

## Flow
10-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_state():
    encoded_state = request.json.get('device_snapshot')
    if not encoded_state:
        return jsonify({'error': 'Missing snapshot'}), 400
    device_blob = base64.b64decode(encoded_state)
    restored_config = pickle.loads(device_blob)
    return jsonify({'status': 'restored', 'device_id': restored_config.get('id')})
```

## Explanation

The application accepts untrusted base64-encoded data from user input, decodes it, and directly deserializes it using pickle.loads() without validation. Python's pickle module can execute arbitrary code during deserialization through __reduce__ methods, allowing attackers to achieve remote code execution by crafting malicious pickled objects.

## Remediation

The fix replaces pickle.loads() with json.loads(), which is a safe deserialization format that cannot execute arbitrary code during parsing. Additionally, a validation function checks that the deserialized configuration only contains expected keys and required fields, providing defense-in-depth against malformed input.

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

app = Flask(__name__)

ALLOWED_DEVICE_KEYS = {'id', 'name', 'firmware_version', 'config', 'network', 'sensors', 'thresholds', 'enabled'}

def validate_device_config(config):
    """Validate that the device configuration only contains expected keys and safe value types."""
    if not isinstance(config, dict):
        return False
    if not config.get('id'):
        return False
    for key in config:
        if key not in ALLOWED_DEVICE_KEYS:
            return False
    return True

@app.route('/iot/device/restore', methods=['POST'])
def restore_device_state():
    encoded_state = request.json.get('device_snapshot')
    if not encoded_state:
        return jsonify({'error': 'Missing snapshot'}), 400
    try:
        device_blob = base64.b64decode(encoded_state)
        restored_config = json.loads(device_blob)
    except (base64.binascii.Error, json.JSONDecodeError) as e:
        return jsonify({'error': 'Invalid snapshot format'}), 400
    if not validate_device_config(restored_config):
        return jsonify({'error': 'Invalid device configuration'}), 400
    return jsonify({'status': 'restored', 'device_id': restored_config.get('id')})
```
