# JSONPickle Deserialization via jsonpickle.decode

Language: Python
Severity: Critical
CWE: CWE-502

## Source
8

## Flow
8-11

## Sink
11

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

app = Flask(__name__)

@app.route('/iot/telemetry/restore', methods=['POST'])
def restore_device_telemetry():
    archived_data = request.json.get('telemetry_snapshot')
    if not archived_data:
        return jsonify({'error': 'Missing telemetry data'}), 400
    restored_metrics = jsonpickle.decode(archived_data)
    device_id = restored_metrics.get('device_id', 'unknown')
    return jsonify({'status': 'restored', 'device': device_id, 'metrics': restored_metrics})
```

## Explanation

The application accepts user-controlled JSON data through the 'telemetry_snapshot' parameter and directly deserializes it using jsonpickle.decode() without validation. Jsonpickle can reconstruct arbitrary Python objects including those with __reduce__ methods, allowing an attacker to execute arbitrary code during deserialization.

## Remediation

The fix replaces jsonpickle.decode() with the safe json.loads() function, which only deserializes data into primitive Python types (dicts, lists, strings, numbers, booleans, None) and cannot instantiate arbitrary objects. Additionally, a validation function ensures that the deserialized data only contains expected telemetry keys with primitive value types, providing defense-in-depth against malformed input.

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

app = Flask(__name__)

ALLOWED_METRIC_KEYS = {'device_id', 'temperature', 'humidity', 'pressure', 'battery_level', 'signal_strength', 'timestamp', 'firmware_version', 'uptime'}

def validate_telemetry_data(data):
    """Validate that telemetry data contains only expected primitive types."""
    if not isinstance(data, dict):
        return False
    for key, value in data.items():
        if key not in ALLOWED_METRIC_KEYS:
            return False
        if not isinstance(value, (str, int, float, bool, type(None))):
            return False
    return True

@app.route('/iot/telemetry/restore', methods=['POST'])
def restore_device_telemetry():
    archived_data = request.json.get('telemetry_snapshot')
    if not archived_data:
        return jsonify({'error': 'Missing telemetry data'}), 400
    try:
        restored_metrics = json.loads(archived_data)
    except (json.JSONDecodeError, TypeError):
        return jsonify({'error': 'Invalid telemetry data format'}), 400
    if not validate_telemetry_data(restored_metrics):
        return jsonify({'error': 'Telemetry data contains invalid or disallowed fields'}), 400
    device_id = restored_metrics.get('device_id', 'unknown')
    return jsonify({'status': 'restored', 'device': device_id, 'metrics': restored_metrics})
```
