Uncontrolled Recursion in orjson (CVE-2024-27454)

The popular JSON parsing and serialization library, orjson, used widely in Python projects, has a Uncontrolled Recursion vulnerability in versions before 3.9.15. This vulnerability allows an attacker to crash applications using orjson through a denial-of-service (DoS) attack.

https://www.cve.org/CVERecord?id=CVE-2024-27454

Severity

CVSS Base Score: 7.5
CVSS v3.1 Vector: AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H&version=3.1

Affected versions

All versions of orjson before 3.9.15 are affected by this vulnerability.

View the full list

Mitigation

Upgrade to orjson 3.9.15 or later. The updated versions raise a JSONDecodeError when the input is too deeply nested, preventing the uncontrolled recursion.

Timeline

Comparing behavior across different packages

To highlight how different JSON packages handle recursive input, a simple test was conducted. This test incrementally increased input depth until an error occurred.

The behavior of orjson was compared against other popular choices. These include the Python standard library json, ujson (ultrajson), rapidjson, simplejson, and msgspec.

View the code
import json
from collections.abc import Callable

import msgspec
import orjson
import rapidjson
import simplejson
import ujson
from humanize import naturalsize
from tabulate import tabulate


def check_parsing(fn: Callable) -> tuple[int, Exception]:
    input_size = 0

    try:
        while True:
            input_size += 2
            input_half_size = input_size // 2
            input_bytes = b'[' * input_half_size + b']' * input_half_size

            fn(input_bytes)
    except Exception as e:
        return input_size, e


def check_serialization(fn: Callable) -> tuple[int, Exception]:
    input_size = 2
    input_data = []

    try:
        while True:
            input_size += 2
            input_data = [input_data]

            fn(input_data)
    except Exception as e:
        return input_size, e


header = ('Package Name', 'Package Version', 'Input Size', 'Exception Type')
parsing_data = []
serialization_data = []

# json-like packages
for module in (json, orjson, ujson, rapidjson, simplejson):
    input_size, e = check_parsing(module.loads)
    human_size = naturalsize(input_size, binary=True)
    parsing_data.append((module.__name__, module.__version__, human_size, type(e).__name__))

    input_size, e = check_serialization(module.dumps)
    human_size = naturalsize(input_size, binary=True)
    serialization_data.append((module.__name__, module.__version__, human_size, type(e).__name__))

# msgspec
input_size, e = check_parsing(msgspec.json.decode)
human_size = naturalsize(input_size, binary=True)
parsing_data.append((msgspec.__name__, msgspec.__version__, human_size, type(e).__name__))

input_size, e = check_serialization(msgspec.json.encode)
human_size = naturalsize(input_size, binary=True)
serialization_data.append((msgspec.__name__, msgspec.__version__, human_size, type(e).__name__))

# summary
print('# Parsing behavior', end='\n\n')
print(tabulate(parsing_data, headers=header, tablefmt='github'), end='\n\n')

print('# Serialization behavior', end='\n\n')
print(tabulate(serialization_data, headers=header, tablefmt='github'))

Parsing behavior

Package Name Package Version Input Size Exception Type
json 2.0.9 2.9 KiB RecursionError
orjson 3.9.15 2.0 KiB JSONDecodeError
ujson 5.9.0 2.0 KiB JSONDecodeError
rapidjson 1.16 2.0 KiB RecursionError
simplejson 3.19.2 2.9 KiB RecursionError
msgspec 0.18.6 2.9 KiB RecursionError

Serialization behavior

Package Name Package Version Input Size Exception Type
json 2.0.9 2.9 KiB RecursionError
orjson 3.9.15 512 Bytes TypeError
ujson 5.9.0 2.0 KiB OverflowError
rapidjson 1.16 2.9 KiB RecursionError
simplejson 3.19.2 2.9 KiB RecursionError
msgspec 0.18.6 2.9 KiB RecursionError

Credits

Credit for discovering this vulnerability goes to David Buchanan. Thanks also go to Kamil Monicz for their analysis of the security implications of this issue.

Submit a correction

If you find any inaccuracies or have suggestions to improve this blog post, please feel free to contact the author https://monicz.dev/#get-in-touch.