# Regex DoS via Catastrophic Backtracking in re.search()

Language: Python
Severity: High
CWE: CWE-1333

## Source
12

## Flow
12-13-6

## Sink
6

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

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

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

MAX_SERIAL_LENGTH = 128

# Compile a safe regex pattern that avoids nested quantifiers
# Format: One or more "IOT-<alphanum>-" segments followed by exactly one "DEVICE-<alphanum>" segment
SERIAL_PATTERN = re.compile(r'^(?:IOT-[A-Z0-9]{1,20}-){1,10}DEVICE-[A-Z0-9]{1,20}$')

def validate_iot_device_serial(serial_number):
    if not serial_number or len(serial_number) > MAX_SERIAL_LENGTH:
        return jsonify({"status": "invalid"})
    if SERIAL_PATTERN.match(serial_number):
        return jsonify({"status": "valid", "serial": serial_number})
    return jsonify({"status": "invalid"})

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