VAITP Dataset

← Back to the dataset

CVE-2024-10624

ReDoS in gradio gr.Datetime via regex `^(?:\s*now\s*…)`, causes high CPU usage.

  • CVSS 7.5
  • CWE-1333
  • Input Validation and Sanitization
  • Remote

A Regular Expression Denial of Service (ReDoS) vulnerability exists in the gradio-app/gradio repository, affecting the gr.Datetime component. The affected version is git commit 98cbcae. The vulnerability arises from the use of a regular expression `^(?:\s*now\s*(?:-\s*(\d+)\s*([dmhs]))?)?\s*$` to process user input. In Python's default regex engine, this regular expression can take polynomial time to match certain crafted inputs. An attacker can exploit this by sending a crafted HTTP request, causing the gradio process to consume 100% CPU and potentially leading to a Denial of Service (DoS) condition on the server.

CVSS base score
7.5
Published
2025-03-20
OWASP
A03 Injection
Orthogonal defect classification
Algorithm
Code defect classification
Incorrect Algorithm
Category
Input Validation and Sanitization
Subcategory
Design Defects
Accessibility scope
Remote
Impact
Denial of Service (DoS)
Affected component
gradio
Fixed by upgrading
Yes

Solution

Upgrade to a version of `gradio` containing a fix for commit 98cbcae, or a later version. Check the Gradio release notes for the specific version number with the fix.

Vulnerable code sample

import re
import time

def vulnerable_datetime_parser(input_string):
    """
    Simulates a vulnerable datetime parsing function using the problematic regex.
    """
    pattern = r"^(?:\s*now\s*(?:-\s*(\d+)\s*([dmhs]))?)?\s*$"
    try:
        match = re.match(pattern, input_string)
        if match:
            print(f"Match found for: {input_string}")
            # Simulate some processing time if a match is found. This is to show that
            # even a 'successful' match can be slow.
            time.sleep(0.001) #Added time.sleep to show slow processing.
            return True
        else:
            print(f"No match found for: {input_string}")
            return False
    except re.error as e:
        print(f"Regex error: {e}")
        return False


if __name__ == "__main__":
    # Example of a vulnerable input
    vulnerable_input = "now" + " " * 100 + "-" + " " * 100 + "1" + " " * 100 +"d" + " " * 100 #String with many spaces
    
    #Example of another vulnerable input
    vulnerable_input_2 = "now" + " " * 20000 #Long string of spaces
    

    start_time = time.time()
    vulnerable_datetime_parser(vulnerable_input)
    end_time = time.time()
    print(f"Processing time for vulnerable input 1: {end_time - start_time:.4f} seconds")

    start_time = time.time()
    vulnerable_datetime_parser(vulnerable_input_2)
    end_time = time.time()
    print(f"Processing time for vulnerable input 2: {end_time - start_time:.4f} seconds")

Patched code sample

import re

def sanitize_datetime_input(user_input):
  """
  Sanitizes datetime input to prevent ReDoS.

  This function replaces the vulnerable regex with a safe and efficient method
  for validating and extracting information from user input. Instead of
  complex backtracking, it uses a combination of simple regex patterns and
  string splitting to achieve the desired functionality.  It now also uses
  a maximum input length to mitigate DoS attempts.

  Args:
    user_input: The user-provided datetime string.

  Returns:
    A tuple containing (valid, offset_value, offset_unit) where:
      valid: True if the input is valid, False otherwise.
      offset_value: The numeric offset value (int) if present, None otherwise.
      offset_unit: The offset unit (str) if present, None otherwise.

  """

  MAX_INPUT_LENGTH = 50  # Prevent excessively long inputs
  if len(user_input) > MAX_INPUT_LENGTH:
        return False, None, None

  # Basic check for "now"
  user_input = user_input.strip()
  if not user_input.startswith("now") and user_input != "":
      return False, None, None

  #If input is exactly "now", no further parsing needed
  if user_input == "now":
        return True, None, None

  # Check for "now - ..." pattern
  parts = user_input.split("-")
  if len(parts) > 2:
        return False, None, None

  if len(parts) == 1:
    return True, None, None #The case where the user input is only "now". Already handled previously, but left to make things very clear.


  # Extract offset part
  offset_part = parts[1].strip()

  # Extract value and unit using a simple regex
  match = re.match(r"(\d+)([dmhs])", offset_part)
  if not match:
      return False, None, None

  offset_value = int(match.group(1))
  offset_unit = match.group(2)

  return True, offset_value, offset_unit



# Example usage demonstrating fix against ReDoS

#Vulnerable Pattern: `^(?:\s*now\s*(?:-\s*(\d+)\s*([dmhs]))?)?\s*$`

#Test cases:
#This example shows a ReDoS exploitation attempt using a long sequence of spaces interspersed with "now" and "-". The original regex used backtracking to process this long string of data, which can take polynomial time to match, leading to a Denial of Service (DoS) condition on the server by consuming 100% CPU.
user_input_vuln = "now" + " " * 50 + "now" + " " * 50 + "now" + " " * 50 + "now" + " " * 50  + "now" #Example of a long string that triggers catastrophic backtracking on the vulnerable Regex

is_valid, offset, unit = sanitize_datetime_input(user_input_vuln)
print(f"Input: {user_input_vuln}, Valid: {is_valid}, Offset: {offset}, Unit: {unit}")

user_input_valid = "now - 10d" #Example of a valid user input
is_valid, offset, unit = sanitize_datetime_input(user_input_valid)
print(f"Input: {user_input_valid}, Valid: {is_valid}, Offset: {offset}, Unit: {unit}")

user_input_invalid = "now - abc" #Example of an invalid user input
is_valid, offset, unit = sanitize_datetime_input(user_input_invalid)
print(f"Input: {user_input_invalid}, Valid: {is_valid}, Offset: {offset}, Unit: {unit}")

user_input_now = "now" #Example of a user input of just "now"
is_valid, offset, unit = sanitize_datetime_input(user_input_now)
print(f"Input: {user_input_now}, Valid: {is_valid}, Offset: {offset}, Unit: {unit}")

user_input_empty = "" #Example of an empty user input
is_valid, offset, unit = sanitize_datetime_input(user_input_empty)
print(f"Input: {user_input_empty}, Valid: {is_valid}, Offset: {offset}, Unit: {unit}")

user_input_long = "now - 10d" * 10 #Example of a long user input string
is_valid, offset, unit = sanitize_datetime_input(user_input_long)
print(f"Input: {user_input_long}, Valid: {is_valid}, Offset: {offset}, Unit: {unit}") # This will return invalid because of max input length check.

Payload

`"now - 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 d"`

Cite this entry

@misc{vaitp:cve202410624,
  title        = {{ReDoS in gradio gr.Datetime via regex `^(?:\s*now\s*...)`, causes high CPU usage.
}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2025},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2024-10624},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2024-10624/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::