{"title":"Regex DoS via Catastrophic Backtracking in re.search()","language":"Python","severity":"High","cwe":"CWE-1333","source_lines":[8],"flow_lines":[8,9,10],"sink_lines":[10],"vulnerable_code":"import re\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\n@app.route('/api/iot/validate_device_serial', methods=['POST'])\ndef validate_iot_device_serial():\n    serial_num = request.json.get('device_serial', '')\n    pattern = r'^(IOT-[A-Z0-9]+-)+(PROD|DEV|TEST)+$'\n    if re.search(pattern, serial_num):\n        return jsonify({'status': 'valid', 'device': serial_num})\n    return jsonify({'status': 'invalid'}), 400","explanation":"The regex pattern '^(IOT-[A-Z0-9]+-)+(PROD|DEV|TEST)+$' contains nested quantifiers that cause catastrophic backtracking. When re.search() processes a malformed serial like 'IOT-ABC-IOT-DEF-IOT-GHI-INVALID', the regex engine exponentially retries combinations, causing CPU exhaustion and denial of service.","remediation":"The fix eliminates catastrophic backtracking by replacing the nested quantifiers `(IOT-[A-Z0-9]+-)+` with a non-capturing group having a bounded repetition `(?:IOT-[A-Z0-9]+-){1,10}`, and removes the unnecessary `+` after the suffix group `(PROD|DEV|TEST)`. Additionally, input length is capped at 128 characters to prevent excessively long inputs from reaching the regex engine, and `re.match()` is used instead of `re.search()` since the pattern is anchored.","secure_code":"import re\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\nMAX_SERIAL_LENGTH = 128\nSERIAL_PATTERN = re.compile(r'^(?:IOT-[A-Z0-9]+-){1,10}(?:PROD|DEV|TEST)$')\n\n@app.route('/api/iot/validate_device_serial', methods=['POST'])\ndef validate_iot_device_serial():\n    serial_num = request.json.get('device_serial', '')\n    if not isinstance(serial_num, str) or len(serial_num) > MAX_SERIAL_LENGTH:\n        return jsonify({'status': 'invalid'}), 400\n    if SERIAL_PATTERN.match(serial_num):\n        return jsonify({'status': 'valid', 'device': serial_num})\n    return jsonify({'status': 'invalid'}), 400"}