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.
- CWE
- CWE-203
- 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 ::
