CVE-2026-44681
Open redirect in Authlib OpenID grants when omitting the 'openid' scope.
- CVSS 6.1
- CWE-601
- Input Validation and Sanitization
- Remote
Authlib is a Python library which builds OAuth and OpenID Connect servers. Prior to 1.6.12 and 1.7.1, an unauthenticated open redirect in Authlib's OpenIDImplicitGrant and OpenIDHybridGrant authorization endpoint lets a remote attacker cause the authorization server to issue an HTTP 302 to an attacker-chosen URL by submitting an authorization request that omits the openid scope. This vulnerability is fixed in 1.6.12 and 1.7.1.
- CWE
- CWE-601
- CVSS base score
- 6.1
- Published
- 2026-05-27
- OWASP
- A01 Broken Access Control
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Input Validation and Sanitization
- Subcategory
- Open Redirects
- Accessibility scope
- Remote
- Impact
- Unauthorized Access
- Affected component
- Authlib
- Fixed by upgrading
- Yes
Solution
Upgrade Authlib to version 1.6.12 or 1.7.1.
Vulnerable code sample
import urllib.parse
# In a real application, this would be fetched from a database
# for a given client_id.
REGISTERED_REDIRECT_URIS = [
'https://my-client-app.com/callback',
'https://my-client-app.com/oidc/cb'
]
def is_uri_registered(uri, client_id):
"""Simulates checking if a redirect_uri is registered and trusted for a client."""
# This is a simplified check for demonstration purposes.
return uri in REGISTERED_REDIRECT_URIS
def create_vulnerable_authorization_response(request_params):
"""
This function simulates the vulnerable logic within Authlib's OpenID
grant types before the patch for CVE-2026-44681.
The vulnerability is an open redirect that occurs when a request
omits the 'openid' scope.
"""
scope = request_params.get('scope', '')
redirect_uri = request_params.get('redirect_uri')
client_id = request_params.get('client_id')
if not redirect_uri:
# In a real scenario, this would return a rendered error page.
return "Error: redirect_uri is required."
# =========================================================================
# VULNERABLE LOGIC BLOCK
#
# If the 'openid' scope is NOT present, the code path incorrectly assumes
# it's a standard OAuth2 request and fails to enforce the strict
# redirect_uri validation required for OpenID Connect, even in OIDC grants.
# This allows the unvalidated `redirect_uri` to be used for the redirect.
# =========================================================================
if 'openid' not in scope.split():
print(f"[!] VULNERABLE PATH TRIGGERED: 'openid' scope is missing.")
print(f"[!] Bypassing redirect_uri validation against registered URIs.")
# The server would then construct and issue an HTTP 302 Redirect.
# The 'Location' header would be set to this untrusted URI.
location = redirect_uri
print(f"[*] Server would redirect to attacker's URL: {location}")
return location # This represents the Location header of the 302 redirect.
# =========================================================================
# SECURE LOGIC BLOCK (for comparison)
#
# If the 'openid' scope IS present, the code correctly performs
# strict validation of the redirect_uri against the client's registered URIs.
# =========================================================================
print(f"[*] Secure Path: 'openid' scope is present.")
if is_uri_registered(redirect_uri, client_id):
print(f"[*] Redirect URI '{redirect_uri}' is valid and registered.")
# A real response would build a proper redirect with params in the fragment.
location = f"{redirect_uri}#access_token=...&state=..."
print(f"[*] Server would redirect to: {location}")
return location
else:
print(f"[!] Redirect URI '{redirect_uri}' is NOT registered for this client.")
return "Error: redirect_uri_mismatch"
if __name__ == '__main__':
print("--- DEMONSTRATING THE VULNERABILITY (CVE-2026-44681) ---\n")
# 1. Attacker's crafted request
# The attacker omits the 'openid' scope and provides a malicious 'redirect_uri'.
# A user tricked into clicking a link with these parameters would be vulnerable.
attacker_request_params = {
'client_id': 'legit-client-123',
'response_type': 'token',
'scope': 'profile email', # 'openid' is deliberately omitted
'redirect_uri': 'https://evil-attacker.com/collect-token'
}
print("--- 1. Simulating Attacker's Request ---")
result = create_vulnerable_authorization_response(attacker_request_params)
print(f"--> Result: Open redirect to '{result}'\n")
# 2. Legitimate user's request (for comparison)
# A valid request includes the 'openid' scope and a registered redirect_uri.
legitimate_request_params = {
'client_id': 'legit-client-123',
'response_type': 'id_token token',
'scope': 'openid profile email', # 'openid' is correctly included
'redirect_uri': 'https://my-client-app.com/callback'
}
print("--- 2. Simulating Legitimate Request ---")
result = create_vulnerable_authorization_response(legitimate_request_params)
print(f"--> Result: Secure redirect as expected.\n")Patched code sample
import typing as t
# A simple custom error to represent an invalid scope error
class InvalidScopeError(Exception):
pass
# A mock request object to simulate the internal state of a grant
class MockRequest:
def __init__(self, scope: str):
# The 'scope' in an OAuth2/OIDC request is a space-delimited string
self.scope = scope
# This class represents a simplified OIDC grant (e.g., OpenIDImplicitGrant)
# containing the logic that fixes the vulnerability.
class PatchedOIDCGrant:
def __init__(self, request: MockRequest):
self.request = request
def validate_authorization_request(self):
"""
Validates an authorization request. This method includes the fix for
the open redirect vulnerability.
The vulnerability was caused by not checking for the 'openid' scope
in OpenID Connect grant types. The fix is to ensure this scope is
present, rejecting the request early if it is missing. This prevents
the server from processing a malformed request that could be used to
trigger a redirect to an attacker-controlled URL.
"""
# THE FIX: Enforce the presence of the "openid" scope for OIDC flows.
if 'openid' not in self.request.scope.split():
raise InvalidScopeError('Missing "openid" scope.')
# If the check passes, other validation logic would continue here,
# eventually leading to a safe redirection.
passPayload
https://vulnerable-auth-server.com/authorize?client_id=some-client-id&response_type=id_token%20token&redirect_uri=https://attacker-controlled-site.com&scope=profile%20email&state=12345&nonce=67890
Cite this entry
@misc{vaitp:cve202644681,
title = {{Open redirect in Authlib OpenID grants when omitting the 'openid' scope.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-44681},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-44681/}}
}
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 ::
