# Regex Denial of Service via Catastrophic Backtracking in re.search

Language: Python
Severity: High
CWE: CWE-1333

## Source
12

## Flow
12-13-4-5

## Sink
5

## Vulnerable Code
```python
import re
from flask import request, jsonify

def validate_iot_device_serial(serial_number):
    pattern = r'^(IOT-([A-Z0-9]+-)*)+(DEVICE-([0-9]+-)*)$'
    if re.search(pattern, serial_number):
        return jsonify({"status": "valid", "serial": serial_number})
    return jsonify({"status": "invalid"})

@app.route('/api/v2/iot/register', methods=['POST'])
def register_iot_device():
    device_serial = request.json.get('device_serial', '')
    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
```python
import re
from flask import request, jsonify

MAX_SERIAL_LENGTH = 256

def validate_iot_device_serial(serial_number):
    if not serial_number or len(serial_number) > MAX_SERIAL_LENGTH:
        return jsonify({"status": "invalid"})
    
    # Use a non-backtracking pattern that matches the expected format:
    # IOT- followed by one or more alphanumeric segments separated by hyphens,
    # then DEVICE- followed by one or more numeric segments separated by hyphens
    pattern = r'^IOT-(?:[A-Z0-9]+-)+DEVICE-(?:[0-9]+-)*[0-9]+$'
    if re.fullmatch(pattern, serial_number):
        return jsonify({"status": "valid", "serial": serial_number})
    return jsonify({"status": "invalid"})

@app.route('/api/v2/iot/register', methods=['POST'])
def register_iot_device():
    device_serial = request.json.get('device_serial', '')
    return validate_iot_device_serial(device_serial)
```
