# Template Injection via str.format_map()

Language: Python
Severity: Critical
CWE: CWE-1336

## Source
1

## Flow
1-7-8

## Sink
8

## Vulnerable Code
```python
def provision_iot_device(device_id, user_params):
    device_template = "Device {device_id} provisioned with firmware={fw_version} and endpoint={endpoint}"
    default_config = {
        'device_id': device_id,
        'fw_version': '2.1.4',
        'endpoint': 'iot.cloud.example.com'
    }
    merged_config = {**default_config, **user_params}
    provisioning_log = device_template.format_map(merged_config)
    return provisioning_log
```

## Explanation

The function accepts untrusted user_params that are merged into merged_config and then passed to format_map(). Python's format_map() allows access to object attributes and methods, enabling attackers to inject format strings that access sensitive globals, execute arbitrary code via __init__.__globals__, or leak information.

## Remediation

The fix applies three layers of defense: (1) a whitelist of allowed parameter keys prevents injection of dangerous keys like '__init__', (2) input validation rejects values containing curly braces that could be used for nested format string attacks, and (3) f-string interpolation with explicit dictionary key access replaces format_map() to eliminate the attribute traversal attack surface entirely.

## Secure Code
```python
def provision_iot_device(device_id, user_params):
    ALLOWED_PARAMS = {'fw_version', 'endpoint'}
    
    default_config = {
        'fw_version': '2.1.4',
        'endpoint': 'iot.cloud.example.com'
    }
    
    # Only allow whitelisted keys from user_params with sanitized string values
    sanitized_params = {}
    for key, value in user_params.items():
        if key in ALLOWED_PARAMS:
            # Ensure value is a plain string and does not contain format specifiers
            sanitized_value = str(value)
            if '{' in sanitized_value or '}' in sanitized_value:
                raise ValueError(f"Invalid characters in parameter '{key}': curly braces are not allowed")
            sanitized_params[key] = sanitized_value
    
    merged_config = {**default_config, **sanitized_params}
    
    # Use safe string concatenation instead of format_map to avoid attribute access
    provisioning_log = (
        f"Device {device_id} provisioned with "
        f"firmware={merged_config['fw_version']} and "
        f"endpoint={merged_config['endpoint']}"
    )
    return provisioning_log
```
