# Unsafe YAML Deserialization via yaml.load() with FullLoader Bypass

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.FullLoader)
        device_id = device_config.get('device_id', 'unknown')
        firmware_ver = device_config.get('firmware_version', '1.0.0')
        capabilities = device_config.get('capabilities', [])
        return jsonify({'status': 'provisioned', 'device_id': device_id, 'firmware': firmware_ver})
    except yaml.YAMLError as e:
        return jsonify({'error': 'Invalid device manifest'}), 400
```

## Explanation

The endpoint accepts untrusted YAML data from request.data and deserializes it using yaml.load() with FullLoader. While FullLoader is safer than the deprecated unsafe Loader, it still allows instantiation of arbitrary Python objects through YAML tags like !!python/object/apply:, enabling remote code execution through crafted YAML payloads.

## Remediation

The fix replaces yaml.load() with yaml.safe_load(), which uses the SafeLoader that only allows basic YAML types (strings, numbers, lists, dicts) and rejects all Python-specific YAML tags that could lead to arbitrary object instantiation or code execution. An additional type check ensures the parsed result is a dictionary before accessing its keys.

## 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({'error': 'Invalid device 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', [])
        return jsonify({'status': 'provisioned', 'device_id': device_id, 'firmware': firmware_ver})
    except yaml.YAMLError as e:
        return jsonify({'error': 'Invalid device manifest'}), 400
```
