# Arbitrary Object Instantiation 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/device/restore', methods=['POST'])
def restore_device_state():
    backup_payload = request.json.get('device_state')
    if not backup_payload:
        return jsonify({'error': 'Missing device state'}), 400
    restored_obj = jsonpickle.decode(backup_payload)
    restored_obj.apply_configuration()
    return jsonify({'status': 'Device restored', 'device_id': restored_obj.device_id})
```

## Explanation

The application accepts untrusted JSON data from the request and passes it directly to jsonpickle.decode(), which can deserialize arbitrary Python objects. An attacker can craft a malicious payload containing dangerous object types that execute code upon deserialization, leading to remote code execution.

## Remediation

The fix removes jsonpickle entirely and replaces it with standard json.loads() for parsing, combined with strict validation of the input against an allowlist of known fields. Instead of deserializing arbitrary objects, the input is parsed as plain JSON data and used to construct a known-safe DeviceState class, eliminating any possibility of arbitrary object instantiation or code execution.

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

app = Flask(__name__)

ALLOWED_DEVICE_FIELDS = {'device_id', 'firmware_version', 'network_config', 'sensor_config', 'thresholds', 'name'}

class DeviceState:
    def __init__(self, config_dict):
        self.device_id = config_dict.get('device_id')
        self.firmware_version = config_dict.get('firmware_version')
        self.network_config = config_dict.get('network_config', {})
        self.sensor_config = config_dict.get('sensor_config', {})
        self.thresholds = config_dict.get('thresholds', {})
        self.name = config_dict.get('name', '')

    def apply_configuration(self):
        # Apply validated configuration to the device
        pass


def validate_device_state(data):
    """Validate that the device state contains only allowed fields and safe value types."""
    if not isinstance(data, dict):
        raise ValueError('Device state must be a JSON object')
    unknown_fields = set(data.keys()) - ALLOWED_DEVICE_FIELDS
    if unknown_fields:
        raise ValueError(f'Unknown fields in device state: {unknown_fields}')
    if 'device_id' not in data or not isinstance(data['device_id'], str):
        raise ValueError('device_id is required and must be a string')
    return True


@app.route('/iot/device/restore', methods=['POST'])
def restore_device_state():
    backup_payload = request.json.get('device_state')
    if not backup_payload:
        return jsonify({'error': 'Missing device state'}), 400

    try:
        config_dict = json.loads(backup_payload) if isinstance(backup_payload, str) else backup_payload
        validate_device_state(config_dict)
        restored_obj = DeviceState(config_dict)
    except (json.JSONDecodeError, ValueError) as e:
        return jsonify({'error': f'Invalid device state: {str(e)}'}), 400

    restored_obj.apply_configuration()
    return jsonify({'status': 'Device restored', 'device_id': restored_obj.device_id})
```
