{"title":"Resource Exhaustion via ast.literal_eval() on Untrusted Input","language":"Python","severity":"High","cwe":"CWE-400","source_lines":[8],"flow_lines":[8,10],"sink_lines":[10],"vulnerable_code":"from flask import Flask, request, jsonify\nimport ast\n\napp = Flask(__name__)\n\n@app.route('/iot/device/config', methods=['POST'])\ndef update_device_settings():\n    device_payload = request.form.get('settings_data', '{}')\n    try:\n        parsed_config = ast.literal_eval(device_payload)\n        device_id = parsed_config.get('device_id', 'unknown')\n        return jsonify({'status': 'success', 'device': device_id, 'updated': True})\n    except (ValueError, SyntaxError) as e:\n        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":"from flask import Flask, request, jsonify\nimport json\n\napp = Flask(__name__)\n\nMAX_PAYLOAD_SIZE = 10240  # 10 KB max for device configuration\nMAX_NESTING_DEPTH = 5\n\ndef check_nesting_depth(obj, max_depth, current_depth=0):\n    if current_depth > max_depth:\n        raise ValueError(\"Configuration nesting depth exceeds maximum allowed\")\n    if isinstance(obj, dict):\n        for key, value in obj.items():\n            check_nesting_depth(value, max_depth, current_depth + 1)\n    elif isinstance(obj, (list, tuple)):\n        for item in obj:\n            check_nesting_depth(item, max_depth, current_depth + 1)\n\n@app.route('/iot/device/config', methods=['POST'])\ndef update_device_settings():\n    device_payload = request.form.get('settings_data', '{}')\n\n    # Validate input size before parsing\n    if len(device_payload) > MAX_PAYLOAD_SIZE:\n        return jsonify({'status': 'error', 'message': 'Configuration payload too large'}), 413\n\n    try:\n        # Use json.loads instead of ast.literal_eval for safer, more predictable parsing\n        parsed_config = json.loads(device_payload)\n\n        # Ensure the parsed result is a dictionary\n        if not isinstance(parsed_config, dict):\n            return jsonify({'status': 'error', 'message': 'Configuration must be a JSON object'}), 400\n\n        # Validate nesting depth to prevent deeply nested structures\n        check_nesting_depth(parsed_config, MAX_NESTING_DEPTH)\n\n        device_id = parsed_config.get('device_id', 'unknown')\n        return jsonify({'status': 'success', 'device': device_id, 'updated': True})\n    except (json.JSONDecodeError) as e:\n        return jsonify({'status': 'error', 'message': 'Invalid configuration format'}), 400\n    except ValueError as e:\n        return jsonify({'status': 'error', 'message': str(e)}), 400"}