# YAML Deserialization Remote Code Execution

Language: Python
Severity: Critical
CWE: CWE-502

## Source
6

## Flow
6-7

## Sink
7

## Vulnerable Code
```python
from flask import Flask, request
import yaml
app = Flask(__name__)
@app.route('/iot/device/config', methods=['POST'])
def sync_device_profile():
    raw_cfg = request.data.decode('utf-8')
    dev_profile = yaml.load(raw_cfg)
    dev_id = dev_profile.get('device_id', 'unknown')
    fw_ver = dev_profile.get('firmware', '0.0.0')
    return {"status": "synced", "id": dev_id, "fw": fw_ver}, 200
```

## Explanation

The raw HTTP request body is decoded and passed directly to yaml.load() without specifying a safe Loader. PyYAML's yaml.load() without a Loader argument (or with the default FullLoader/UnsafeLoader in older versions) allows deserialization of 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_cfg)` with `yaml.safe_load(raw_cfg)`, which only deserializes standard YAML types (strings, numbers, lists, dicts) and refuses to construct arbitrary Python objects from tags like `!!python/object/apply`. An additional type check ensures the parsed result is a dictionary before accessing keys, preventing unexpected behavior from non-mapping YAML inputs.

## Secure Code
```python
from flask import Flask, request
import yaml
app = Flask(__name__)
@app.route('/iot/device/config', methods=['POST'])
def sync_device_profile():
    raw_cfg = request.data.decode('utf-8')
    dev_profile = yaml.safe_load(raw_cfg)
    if not isinstance(dev_profile, dict):
        return {"status": "error", "message": "Invalid configuration format"}, 400
    dev_id = dev_profile.get('device_id', 'unknown')
    fw_ver = dev_profile.get('firmware', '0.0.0')
    return {"status": "synced", "id": dev_id, "fw": fw_ver}, 200
```
