# Unsafe jsonpickle Deserialization via decode()

Language: Python
Severity: Critical
CWE: CWE-502

## Source
8

## Flow
8-10

## Sink
10

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

app = Flask(__name__)

@app.route('/iot/device/restore', methods=['POST'])
def restore_device_config():
    backup_payload = request.form.get('config_backup')
    if backup_payload:
        restored_config = jsonpickle.decode(backup_payload)
        device_id = restored_config.get('device_id', 'unknown')
        apply_device_settings(restored_config)
        return {'status': 'success', 'device': device_id, 'message': 'Configuration restored'}
    return {'status': 'error', 'message': 'No backup data provided'}, 400

def apply_device_settings(config):
    pass
```

## Explanation

The application accepts untrusted user input via request.form.get('config_backup') and directly deserializes it using jsonpickle.decode() without validation. jsonpickle can deserialize arbitrary Python objects including those with __reduce__ methods, allowing attackers to execute arbitrary code during deserialization.

## Remediation

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

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

app = Flask(__name__)

ALLOWED_CONFIG_KEYS = {'device_id', 'device_name', 'network', 'sensors', 'schedule', 'firmware_version', 'timezone', 'thresholds'}

def validate_config(config):
    """Validate that the configuration only contains expected keys and safe value types."""
    if not isinstance(config, dict):
        return False
    for key in config:
        if key not in ALLOWED_CONFIG_KEYS:
            return False
        value = config[key]
        if not isinstance(value, (str, int, float, bool, list, dict, type(None))):
            return False
    return True

@app.route('/iot/device/restore', methods=['POST'])
def restore_device_config():
    backup_payload = request.form.get('config_backup')
    if backup_payload:
        try:
            restored_config = json.loads(backup_payload)
        except (json.JSONDecodeError, ValueError):
            return {'status': 'error', 'message': 'Invalid JSON format'}, 400
        if not validate_config(restored_config):
            return {'status': 'error', 'message': 'Invalid configuration structure'}, 400
        device_id = restored_config.get('device_id', 'unknown')
        apply_device_settings(restored_config)
        return {'status': 'success', 'device': device_id, 'message': 'Configuration restored'}
    return {'status': 'error', 'message': 'No backup data provided'}, 400

def apply_device_settings(config):
    pass
```
