{"title":"Regex Denial of Service (ReDoS) via Catastrophic Backtracking","language":"Python","severity":"High","cwe":"CWE-1333","source_lines":[6],"flow_lines":[6,7,8],"sink_lines":[8],"vulnerable_code":"import re\nfrom flask import Flask, request, jsonify\napp = Flask(__name__)\n@app.route('/api/iot/telemetry/validate', methods=['POST'])\ndef validate_sensor_payload():\n    raw_data = request.json.get('sensor_reading', '')\n    fmt_pattern = re.compile(r'^(\\d+\\.?\\d*)+\\s*(kg|lb|g|oz|mg)+$')\n    match_result = fmt_pattern.match(raw_data)\n    if not match_result:\n        return jsonify({'status': 'invalid', 'msg': 'Sensor reading format rejected'}), 400\n    return jsonify({'status': 'ok', 'parsed': match_result.group()}), 200","explanation":"The untrusted input 'raw_data' is taken directly from user-controlled JSON (line 6) and matched against a regex pattern containing nested quantifiers '(\\d+\\.?\\d*)+' (line 7). This pattern causes catastrophic backtracking when applied to crafted inputs like '1111111111111111111x', because the regex engine exhaustively tries all combinations of how the outer '+' and inner '+' can partition the digit sequence before failing, leading to exponential time complexity and server thread exhaustion.","remediation":"The fix removes the nested quantifiers that caused catastrophic backtracking. The original pattern `(\\d+\\.?\\d*)+` wrapped a repeating group inside another repetition; this has been replaced with the linear-time equivalent `\\d+\\.?\\d*` (no outer group repetition). The unit suffix group's unnecessary `+` quantifier was also removed, and the unit alternatives are placed in a non-capturing group. Additionally, an input-length check and type validation are applied before regex matching to provide defense-in-depth against oversized payloads.","secure_code":"import re\nfrom flask import Flask, request, jsonify\napp = Flask(__name__)\n\n# Precompiled regex without nested quantifiers to prevent catastrophic backtracking.\n# Matches a numeric value (integer or decimal) followed by optional whitespace and a unit.\n_SENSOR_READING_RE = re.compile(r'^\\d+\\.?\\d*\\s*(?:kg|lb|g|oz|mg)$')\n\n# Maximum allowed length for a sensor reading string to bound processing time.\n_MAX_READING_LEN = 64\n\n@app.route('/api/iot/telemetry/validate', methods=['POST'])\ndef validate_sensor_payload():\n    raw_data = request.json.get('sensor_reading', '')\n\n    # Guard: reject excessively long inputs before any regex processing.\n    if not isinstance(raw_data, str) or len(raw_data) > _MAX_READING_LEN:\n        return jsonify({'status': 'invalid', 'msg': 'Sensor reading format rejected'}), 400\n\n    match_result = _SENSOR_READING_RE.match(raw_data)\n    if not match_result:\n        return jsonify({'status': 'invalid', 'msg': 'Sensor reading format rejected'}), 400\n    return jsonify({'status': 'ok', 'parsed': match_result.group()}), 200"}