# YAML Deserialization Remote Code Execution

Language: Python
Severity: Critical
CWE: CWE-502

## Source
6

## Flow
6-7-8

## Sink
8

## Vulnerable Code
```python
import yaml
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/api/iot/device/config', methods=['POST'])
def push_device_config():
    raw_payload = request.data.decode('utf-8')
    try:
        device_cfg = yaml.load(raw_payload)
        dev_id = device_cfg.get('device_id', 'unknown')
        threshold = device_cfg.get('threshold', 75)
        return jsonify({'status': 'applied', 'device': dev_id, 'threshold': threshold})
    except Exception as exc:
        return jsonify({'error': str(exc)}), 400
```

## Explanation

The raw HTTP request body is decoded from untrusted user input on line 6 and passed directly to yaml.load() on line 8 without specifying a safe Loader argument. yaml.load() with no Loader (or with the default FullLoader/UnsafeLoader) can deserialize arbitrary Python objects via YAML tags like !!python/object/apply:os.system, enabling remote code execution on the server.

## Remediation

The fix replaces `yaml.load(raw_payload)` with `yaml.safe_load(raw_payload)`, which only deserializes standard YAML types (strings, numbers, lists, dicts) and refuses to instantiate arbitrary Python objects from YAML tags like `!!python/object/apply`. Additionally, a type check ensures the parsed result is a dictionary as expected, and the exception handler is narrowed to `yaml.YAMLError` to avoid masking unrelated errors.

## Secure Code
```python
import yaml
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/api/iot/device/config', methods=['POST'])
def push_device_config():
    raw_payload = request.data.decode('utf-8')
    try:
        device_cfg = yaml.safe_load(raw_payload)
        if not isinstance(device_cfg, dict):
            return jsonify({'error': 'Invalid configuration format: expected a YAML mapping'}), 400
        dev_id = device_cfg.get('device_id', 'unknown')
        threshold = device_cfg.get('threshold', 75)
        return jsonify({'status': 'applied', 'device': dev_id, 'threshold': threshold})
    except yaml.YAMLError as exc:
        return jsonify({'error': str(exc)}), 400
```
