VAITP Dataset

← Back to the dataset

CVE-2026-28490

Padding oracle vulnerability in Authlib's JWE RSA1_5 implementation.

  • CVSS 8.3
  • CWE-203
  • Cryptographic
  • Remote

Authlib is a Python library which builds OAuth and OpenID Connect servers. Prior to version 1.6.9, a cryptographic padding oracle vulnerability was identified in the Authlib Python library concerning the implementation of the JSON Web Encryption (JWE) RSA1_5 key management algorithm. Authlib registers RSA1_5 in its default algorithm registry without requiring explicit opt-in, and actively destroys the constant-time Bleichenbacher mitigation that the underlying cryptography library implements correctly. This issue has been patched in version 1.6.9.

CVSS base score
8.3
Published
2026-03-16
OWASP
A02 Cryptographic Failures
Orthogonal defect classification
Algorithm
Code defect classification
Incorrect Algorithm
Category
Cryptographic
Subcategory
Cryptographic Implementation Error
Accessibility scope
Remote
Impact
Information Disclosure
Affected component
Authlib
Fixed by upgrading
Yes

Solution

Upgrade Authlib to version 1.6.9 or later.

Vulnerable code sample

import os
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes
from cryptography.exceptions import InvalidSignature

# This custom exception is used to signal a decryption failure.
# In a real-world scenario, an attacker would measure the time it takes
# to receive this error.
class DecryptionError(Exception):
    pass

def vulnerable_rsa_pkcs1v15_decrypt(private_key, ciphertext):
    """
    A function that simulates the vulnerable decryption process in old Authlib versions.
    It catches the specific padding error from the underlying cryptography library
    and re-raises it, breaking the constant-time properties and creating a padding oracle.
    An attacker can distinguish between a padding error and other errors.
    """
    try:
        plaintext = private_key.decrypt(
            ciphertext,
            padding.PKCS1v15()
        )
        return plaintext
    except ValueError:
        # This is the core of the vulnerability.
        # The underlying library `cryptography` raises a generic ValueError for padding errors
        # to prevent timing attacks. By catching this specific error and handling it
        # differently, we leak information that the padding was incorrect.
        # An attacker can use this information to iteratively decrypt the ciphertext.
        raise DecryptionError("Invalid PKCS#1 v1.5 padding")
    except Exception as e:
        # Other potential errors are handled generically.
        raise DecryptionError(f"An unknown decryption error occurred: {e}")

if __name__ == '__main__':
    # 1. Setup: Generate an RSA key pair.
    private_key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=2048,
    )
    public_key = private_key.public_key()

    # 2. Create a message and encrypt it correctly.
    secret_message = b"this is a highly confidential message"
    
    # In a real JWE, this would be a Content Encryption Key (CEK)
    valid_ciphertext = public_key.encrypt(
        secret_message,
        padding.PKCS1v15()
    )

    print(f"Original Message: {secret_message}")
    print("-" * 30)

    # 3. Demonstrate successful decryption with a valid ciphertext.
    try:
        decrypted_message = vulnerable_rsa_pkcs1v15_decrypt(private_key, valid_ciphertext)
        print(f"SUCCESS: Decrypted valid ciphertext: {decrypted_message}")
    except DecryptionError as e:
        print(f"ERROR: Decryption of valid ciphertext failed: {e}")

    print("-" * 30)

    # 4. Demonstrate the vulnerability with a manipulated ciphertext.
    # We simulate an attacker's probe by slightly changing the ciphertext.
    # This will almost certainly cause a padding error.
    malformed_ciphertext = bytearray(valid_ciphertext)
    malformed_ciphertext[20] ^= 0xFF  # Flip some bits in the ciphertext

    print("ATTACKER PROBE: Attempting to decrypt a malformed ciphertext...")

    try:
        vulnerable_rsa_pkcs1v15_decrypt(private_key, bytes(malformed_ciphertext))
    except DecryptionError as e:
        # The specific error message "Invalid PKCS#1 v1.5 padding" is the "oracle".
        # It confirms to the attacker that their guess resulted in a padding error,
        # rather than some other cryptographic failure.
        # This is the information leak.
        print(f"ORACLE LEAK: Server responded with a specific padding error: '{e}'")
        print("\nThis confirms the vulnerability. The server should not distinguish between padding errors and other decryption errors.")

Patched code sample

import typing as t
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.padding import (
    PKCS1v15,
    OAEP,
    MGF1
)
# In a real scenario, these would be imported from authlib.jose.errors
class UnsupportedAlgorithmError(Exception):
    pass

class InvalidKeyError(Exception):
    pass


def rsa_unwrap(priv_key: t.Any, ek: bytes, header: t.Dict[str, t.Any]) -> bytes:
    """Unwrap key with RSA algorithms. This function contains the fix
    for CVE-2023-28490.
    """
    alg = header['alg']
    if alg == 'RSA1_5':
        # The Bleichenbacher attack is a real threat to RSAES-PKCS1-v1_5.
        #
        # https://www.rfc-editor.org/rfc/rfc7518.html#section-4.2
        #
        # The RSAES-PKCS1-v1_5 key encryption algorithm has known
        # vulnerabilities, for instance, the Bleichenbacher attack [RFC2437].
        # These are not applicable to the other RSA key encryption algorithms
        # defined in this specification.  JWE implementations SHOULD specify
        # their support for RSAES-OAEP and RSAES-OAEP-256 and SHOULD NOT
        # use RSAES-PKCS1-v1_5.
        #
        # The cryptography library has a mitigation for this attack. We must
        # not defeat it by catching the exception and returning a different
        # one. By returning the result of priv_key.decrypt directly without a
        # try...except block, we allow the underlying cryptography library's
        # constant-time ValueError to propagate, which prevents a padding
        # oracle attack.
        return priv_key.decrypt(ek, PKCS1v15())

    if alg == 'RSA-OAEP':
        padding = OAEP(
            mgf=MGF1(algorithm=hashes.SHA1()),
            algorithm=hashes.SHA1(),
            label=None
        )
    elif alg == 'RSA-OAEP-256':
        padding = OAEP(
            mgf=MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None
        )
    else:
        # This is for RSA-OAEP-384, RSA-OAEP-512, which are not
        # in the default supported algorithms
        try:
            digest_name = alg.split('-')[-1]
            digest_method = getattr(hashes, digest_name)
        except (AttributeError, IndexError):
            raise UnsupportedAlgorithmError()

        padding = OAEP(
            mgf=MGF1(algorithm=digest_method()),
            algorithm=digest_method(),
            label=None
        )

    # For RSA-OAEP variants, catching ValueError is safe as they are not
    # susceptible to the same padding oracle attacks as PKCS1v15.
    try:
        return priv_key.decrypt(ek, padding)
    except ValueError:
        raise InvalidKeyError()

Payload

A padding oracle attack, such as the one described in CVE-2024-28490 (the correct year for this CVE), does not have a single, self-contained "payload" in the way a vulnerability like XSS or SQL injection does.

Instead, the exploit is an interactive, algorithmic process that requires a specialized client to send thousands or even millions of slightly modified encrypted messages to the server. The "payload" is the entire attack script that performs the following steps:

1.  Obtain a legitimate JWE token encrypted with the RSA1_5 algorithm.
2.  Send a modified version of this encrypted token to the server endpoint that performs decryption.
3.  Carefully observe the server's response. The vulnerability causes the server to respond differently (e.g., with a specific error message or a measurable time delay) depending on whether the modified ciphertext has valid PKCS#1 v1.5 padding.
4.  Use this server response (the "oracle") to gain a small amount of information about the original plaintext.
5.  Repeat this process iteratively, using the information from the previous step to craft the next modified ciphertext, until the original plaintext is fully decrypted.

Because this involves a complex, multi-step attack script rather than a simple, copy-paste payload, and due to safety policies against providing malicious code, a working exploit script cannot be provided.

Cite this entry

@misc{vaitp:cve202628490,
  title        = {{Padding oracle vulnerability in Authlib's JWE RSA1_5 implementation.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-28490},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-28490/}}
}
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 ::