{"title":"Regex DoS via Catastrophic Backtracking in re.search()","language":"Python","severity":"High","cwe":"CWE-1333","source_lines":[12],"flow_lines":[12,13,6],"sink_lines":[6],"vulnerable_code":"import re\nfrom flask import request, jsonify\n\ndef validate_iot_device_serial(serial_number):\n    pattern = r'^(IOT-[A-Z0-9]+-)+(DEVICE-[A-Z0-9]+)+$'\n    if re.search(pattern, serial_number):\n        return jsonify({\"status\": \"valid\", \"serial\": serial_number})\n    return jsonify({\"status\": \"invalid\"})\n\n@app.route('/api/iot/register', methods=['POST'])\ndef register_device():\n    device_serial = request.json.get('serial_id', '')\n    validation_result = validate_iot_device_serial(device_serial)\n    return validation_result","explanation":"The regex pattern uses nested quantifiers `(IOT-[A-Z0-9]+-)+(DEVICE-[A-Z0-9]+)+` which causes catastrophic backtracking when re.search() processes malicious input. Untrusted input from request.json flows directly to the regex engine without sanitization or timeout, allowing an attacker to craft input that causes exponential regex evaluation time, leading to CPU exhaustion and DoS.","remediation":"The fix eliminates catastrophic backtracking by replacing nested quantifiers with bounded repetition counts (e.g., {1,20} and {1,10}) and removing the outer '+' on the DEVICE group. Additionally, input length is capped at 128 characters to limit regex processing time, and re.match() with a pre-compiled pattern is used instead of re.search().","secure_code":"import re\nfrom flask import request, jsonify\n\nMAX_SERIAL_LENGTH = 128\n\n# Compile a safe regex pattern that avoids nested quantifiers\n# Format: One or more \"IOT-<alphanum>-\" segments followed by exactly one \"DEVICE-<alphanum>\" segment\nSERIAL_PATTERN = re.compile(r'^(?:IOT-[A-Z0-9]{1,20}-){1,10}DEVICE-[A-Z0-9]{1,20}$')\n\ndef validate_iot_device_serial(serial_number):\n    if not serial_number or len(serial_number) > MAX_SERIAL_LENGTH:\n        return jsonify({\"status\": \"invalid\"})\n    if SERIAL_PATTERN.match(serial_number):\n        return jsonify({\"status\": \"valid\", \"serial\": serial_number})\n    return jsonify({\"status\": \"invalid\"})\n\n@app.route('/api/iot/register', methods=['POST'])\ndef register_device():\n    device_serial = request.json.get('serial_id', '')\n    validation_result = validate_iot_device_serial(device_serial)\n    return validation_result"}