# Regex Denial of Service (ReDoS) via Catastrophic Backtracking

Language: Python
Severity: High
CWE: CWE-1333

## Source
6

## Flow
6-7-8

## Sink
8

## Vulnerable Code
```python
import re
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/api/iot/device/register', methods=['POST'])
def register_device():
    dev_payload = request.json.get('device_firmware_version', '')
    pattern = re.compile(r'^(([a-zA-Z0-9]+\.)+[a-zA-Z0-9]+\-?)+$')
    if not pattern.match(dev_payload):
        return jsonify({'status': 'error', 'msg': 'Invalid firmware version format'}), 400
    return jsonify({'status': 'ok', 'registered': dev_payload}), 200
```

## Explanation

The untrusted input `dev_payload` is read from the HTTP request body on line 6 and directly passed to `pattern.match()` on line 8. The regex pattern on line 7 contains nested quantifiers `(([a-zA-Z0-9]+\.)+[a-zA-Z0-9]+\-?)+` which cause catastrophic backtracking when a crafted input is provided, leading to exponential CPU time consumption and a Denial of Service.

## Remediation

The fix replaces the vulnerable regex that had nested quantifiers `(([a-zA-Z0-9]+\.)+[a-zA-Z0-9]+\-?)+` with a safe, linear-time regex `^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*(?:-[a-zA-Z0-9]+)?$` that uses only non-nested quantifiers and non-capturing groups, completely eliminating catastrophic backtracking. Additionally, an input length limit (128 characters) and a type check are enforced before regex evaluation to provide defense-in-depth against oversized or malformed inputs.

## Secure Code
```python
import re
from flask import Flask, request, jsonify
app = Flask(__name__)

MAX_FIRMWARE_VERSION_LENGTH = 128
# Linear-time regex: no nested quantifiers, matches patterns like "abc.def.ghi" or "abc.def.ghi-1"
FIRMWARE_VERSION_PATTERN = re.compile(r'^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*(?:-[a-zA-Z0-9]+)?$')

@app.route('/api/iot/device/register', methods=['POST'])
def register_device():
    dev_payload = request.json.get('device_firmware_version', '')
    if not isinstance(dev_payload, str) or len(dev_payload) > MAX_FIRMWARE_VERSION_LENGTH:
        return jsonify({'status': 'error', 'msg': 'Invalid firmware version format'}), 400
    if not FIRMWARE_VERSION_PATTERN.match(dev_payload):
        return jsonify({'status': 'error', 'msg': 'Invalid firmware version format'}), 400
    return jsonify({'status': 'ok', 'registered': dev_payload}), 200
```
