{"title":"Regex DoS via Catastrophic Backtracking in re.match()","language":"Python","severity":"High","cwe":"CWE-1333","source_lines":[9],"flow_lines":[9,12],"sink_lines":[12],"vulnerable_code":"import re\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\n@app.route('/api/iot/device/validate', methods=['POST'])\ndef validate_device_identifier():\n    device_id = request.json.get('device_identifier', '')\n    pattern = r'^([a-zA-Z0-9]+)*-([a-zA-Z0-9]+)*-([a-zA-Z0-9]+)*$'\n    \n    if re.match(pattern, device_id):\n        return jsonify({\n            'status': 'valid',\n            'device_id': device_id,\n            'registered': True\n        }), 200\n    else:\n        return jsonify({\n            'status': 'invalid',\n            'error': 'Device identifier format incorrect'\n        }), 400","explanation":"The regex pattern uses nested quantifiers `([a-zA-Z0-9]+)*` which causes catastrophic backtracking when matching fails. Untrusted input from request.json flows directly to re.match() without sanitization or timeout, allowing attackers to cause exponential time complexity and DoS.","remediation":"The fix removes the nested quantifiers `([a-zA-Z0-9]+)*` which caused catastrophic backtracking and replaces them with simple `[a-zA-Z0-9]+` character class quantifiers that match the same valid inputs without exponential time complexity. Additionally, an input length check (max 128 characters) is added as a defense-in-depth measure to limit processing time on any input.","secure_code":"import re\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\n@app.route('/api/iot/device/validate', methods=['POST'])\ndef validate_device_identifier():\n    device_id = request.json.get('device_identifier', '')\n    \n    # Limit input length to prevent abuse\n    if not device_id or len(device_id) > 128:\n        return jsonify({\n            'status': 'invalid',\n            'error': 'Device identifier format incorrect'\n        }), 400\n    \n    # Fixed regex: removed nested quantifiers that caused catastrophic backtracking\n    # Each group now uses a single quantifier [a-zA-Z0-9]+ without wrapping in ()+*\n    pattern = r'^[a-zA-Z0-9]+-[a-zA-Z0-9]+-[a-zA-Z0-9]+$'\n    \n    if re.match(pattern, device_id):\n        return jsonify({\n            'status': 'valid',\n            'device_id': device_id,\n            'registered': True\n        }), 200\n    else:\n        return jsonify({\n            'status': 'invalid',\n            'error': 'Device identifier format incorrect'\n        }), 400"}