# YAML Deserialization via yaml.load() with Untrusted Input

Language: Python
Severity: Critical
CWE: CWE-502

## Source
8

## Flow
8-10

## Sink
10

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

app = Flask(__name__)

@app.route('/iot/device/provision', methods=['POST'])
def provision_iot_device():
    device_manifest = request.data.decode('utf-8')
    try:
        device_config = yaml.load(device_manifest, Loader=yaml.Loader)
        device_id = device_config.get('device_id', 'unknown')
        firmware_ver = device_config.get('firmware_version', '1.0.0')
        capabilities = device_config.get('capabilities', [])
        security_profile = device_config.get('security_profile', 'default')
        
        registry_entry = {
            'id': device_id,
            'firmware': firmware_ver,
            'caps': capabilities,
            'security': security_profile,
            'status': 'provisioned'
        }
        return jsonify({'success': True, 'device': registry_entry}), 200
    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 400
```

## Explanation

The code uses yaml.load() with yaml.Loader on untrusted user input from request.data without validation. This allows arbitrary Python object instantiation through YAML deserialization, enabling remote code execution via specially crafted YAML payloads containing malicious Python objects.

## Remediation

The fix replaces yaml.load() with yaml.safe_load(), which only deserializes standard YAML tags (strings, numbers, lists, dicts) and prevents instantiation of arbitrary Python objects. An additional type check ensures the parsed YAML is a dictionary before accessing keys, preventing unexpected behavior from non-mapping YAML inputs.

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

app = Flask(__name__)

@app.route('/iot/device/provision', methods=['POST'])
def provision_iot_device():
    device_manifest = request.data.decode('utf-8')
    try:
        device_config = yaml.safe_load(device_manifest)
        if not isinstance(device_config, dict):
            return jsonify({'success': False, 'error': 'Invalid YAML manifest format'}), 400
        device_id = device_config.get('device_id', 'unknown')
        firmware_ver = device_config.get('firmware_version', '1.0.0')
        capabilities = device_config.get('capabilities', [])
        security_profile = device_config.get('security_profile', 'default')
        
        registry_entry = {
            'id': device_id,
            'firmware': firmware_ver,
            'caps': capabilities,
            'security': security_profile,
            'status': 'provisioned'
        }
        return jsonify({'success': True, 'device': registry_entry}), 200
    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 400
```
