{"title":"Regex Denial of Service via Catastrophic Backtracking in re.search","language":"Python","severity":"High","cwe":"CWE-1333","source_lines":[12],"flow_lines":[12,13,4,5],"sink_lines":[5],"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-([0-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/v2/iot/register', methods=['POST'])\ndef register_iot_device():\n    device_serial = request.json.get('device_serial', '')\n    return validate_iot_device_serial(device_serial)","explanation":"The regex pattern contains nested quantifiers (([A-Z0-9]+-)*)+ and (([0-9]+-)*)+ which cause catastrophic backtracking when matching malicious inputs. An attacker can send a specially crafted serial number that forces the regex engine to explore exponentially many matching paths, causing CPU exhaustion and denial of service.","remediation":"The fix removes the nested quantifiers that caused catastrophic backtracking by flattening the regex pattern into a simple, linear structure. The new pattern `^IOT-(?:[A-Z0-9]+-)+DEVICE-(?:[0-9]+-)*[0-9]+$` matches the same intended format (IOT- prefix with alphanumeric segments, then DEVICE- with numeric segments) without any nested repetition. Additionally, an input length limit is enforced to further mitigate potential abuse, and `re.fullmatch` is used instead of `re.search` for stricter matching.","secure_code":"import re\nfrom flask import request, jsonify\n\nMAX_SERIAL_LENGTH = 256\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    \n    # Use a non-backtracking pattern that matches the expected format:\n    # IOT- followed by one or more alphanumeric segments separated by hyphens,\n    # then DEVICE- followed by one or more numeric segments separated by hyphens\n    pattern = r'^IOT-(?:[A-Z0-9]+-)+DEVICE-(?:[0-9]+-)*[0-9]+$'\n    if re.fullmatch(pattern, serial_number):\n        return jsonify({\"status\": \"valid\", \"serial\": serial_number})\n    return jsonify({\"status\": \"invalid\"})\n\n@app.route('/api/v2/iot/register', methods=['POST'])\ndef register_iot_device():\n    device_serial = request.json.get('device_serial', '')\n    return validate_iot_device_serial(device_serial)"}