# Resource Exhaustion via ast.literal_eval() on Untrusted Input

Language: Python
Severity: High
CWE: CWE-400

## Source
8

## Flow
8-10

## Sink
10

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

app = Flask(__name__)

@app.route('/iot/device/config', methods=['POST'])
def update_device_settings():
    device_payload = request.form.get('settings_data', '{}')
    try:
        parsed_config = ast.literal_eval(device_payload)
        device_id = parsed_config.get('device_id', 'unknown')
        return jsonify({'status': 'success', 'device': device_id, 'updated': True})
    except (ValueError, SyntaxError) as e:
        return jsonify({'status': 'error', 'message': 'Invalid configuration format'}), 400
```

## Explanation

The endpoint accepts untrusted user input via request.form.get() without size validation and passes it directly to ast.literal_eval(). An attacker can send extremely large nested data structures that cause excessive CPU and memory consumption during parsing, leading to resource exhaustion and denial of service.

## Remediation

The fix addresses resource exhaustion by: (1) enforcing a maximum payload size limit (10 KB) before any parsing occurs, rejecting oversized inputs early; (2) replacing ast.literal_eval() with json.loads() which is more predictable and efficient for structured data parsing; (3) adding a nesting depth check after parsing to prevent deeply nested structures from causing issues in downstream processing.

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

app = Flask(__name__)

MAX_PAYLOAD_SIZE = 10240  # 10 KB max for device configuration
MAX_NESTING_DEPTH = 5

def check_nesting_depth(obj, max_depth, current_depth=0):
    if current_depth > max_depth:
        raise ValueError("Configuration nesting depth exceeds maximum allowed")
    if isinstance(obj, dict):
        for key, value in obj.items():
            check_nesting_depth(value, max_depth, current_depth + 1)
    elif isinstance(obj, (list, tuple)):
        for item in obj:
            check_nesting_depth(item, max_depth, current_depth + 1)

@app.route('/iot/device/config', methods=['POST'])
def update_device_settings():
    device_payload = request.form.get('settings_data', '{}')

    # Validate input size before parsing
    if len(device_payload) > MAX_PAYLOAD_SIZE:
        return jsonify({'status': 'error', 'message': 'Configuration payload too large'}), 413

    try:
        # Use json.loads instead of ast.literal_eval for safer, more predictable parsing
        parsed_config = json.loads(device_payload)

        # Ensure the parsed result is a dictionary
        if not isinstance(parsed_config, dict):
            return jsonify({'status': 'error', 'message': 'Configuration must be a JSON object'}), 400

        # Validate nesting depth to prevent deeply nested structures
        check_nesting_depth(parsed_config, MAX_NESTING_DEPTH)

        device_id = parsed_config.get('device_id', 'unknown')
        return jsonify({'status': 'success', 'device': device_id, 'updated': True})
    except (json.JSONDecodeError) as e:
        return jsonify({'status': 'error', 'message': 'Invalid configuration format'}), 400
    except ValueError as e:
        return jsonify({'status': 'error', 'message': str(e)}), 400
```
