{"id":"CVE-2026-114620","aliases":["DEBIAN-CVE-2026-114620","EUVD-2026-108522","GHSA-x4rv-c3wx-4cr8","PYSEC-2026-4360","UBUNTU-CVE-2026-114620"],"status":"published","title":"Authlib: Unvalidated request_uri fetch in JWT-secured authorization request (JAR) leading to SSRF","description":"Authlib is a Python library which builds OAuth and OpenID Connect servers. Prior to 1.9.0, Authlib's RFC 9101 JWTAuthenticationRequest passes the unauthenticated, client-controlled request_uri parameter to the abstract get_request_object() hook before applying a destination or registration policy; the hook itself performs no network request, while the documentation demonstrates an unrestricted HTTP fetch. In deployments whose integrator implements that hook as an unrestricted outbound fetch, a remote attacker who knows a valid public client identifier can make the authorization server request internal, link-local, or otherwise unintended URLs and can distinguish fetch outcomes through different error responses and timing. Deployments whose custom hook already applies a secure allowlist, destination controls, and retrieval limits are not affected. The JWTAuthenticationRequest implementation in authlib/oauth2/rfc9101/authorization_server.py forwards request_uri to get_request_object() before an application-approved retrieval policy accepts the destination. This issue is fixed in version 1.9.0.","severity":{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00385,"percentile":0.33069,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-918"],"published":"2026-10-22T21:34:11Z","modified":"2026-11-01T02:30:35Z","fixes":[{"ecosystem":"PyPI","package":"authlib","fixed_in":["1.9.0"],"sources":["ghsa","osv"]},{"ecosystem":"Debian:14","package":"python-authlib","fixed_in":["1.9.1-1"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"1.6.0"},{"fixed":"1.9.0"}]}]},{"source":"osv","ecosystem":"Debian:12","package":"python-authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.2.0-1","1.2.0-1+deb12u1","1.2.0-1+deb12u2","1.2.1-1","1.3.0-1","1.3.0-2","1.3.0-3","1.3.1-1","1.3.2-1","1.3.2-2","1.4.0-1","1.4.1-1","1.5.0-1","1.5.1-1","1.5.2-1","1.6.0-1","1.6.1-1","1.6.3-1","1.6.4-1","1.6.5-1","1.6.6-1","1.6.7-1","1.6.8-1","1.6.9-1","1.7.0-1","1.7.2-1","1.7.2-2","1.9.1-1"]},{"source":"osv","ecosystem":"Debian:13","package":"python-authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.6.0-1","1.6.0-1+deb13u1","1.6.1-1","1.6.3-1","1.6.4-1","1.6.5-1","1.6.6-1","1.6.7-1","1.6.8-1","1.6.9-1","1.7.0-1","1.7.2-1","1.7.2-2","1.9.1-1"]},{"source":"osv","ecosystem":"Debian:14","package":"python-authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.9.1-1"}]}],"versions":["1.6.0-1","1.6.1-1","1.6.3-1","1.6.4-1","1.6.5-1","1.6.6-1","1.6.7-1","1.6.8-1","1.6.9-1","1.7.0-1","1.7.2-1","1.7.2-2"]},{"source":"osv","ecosystem":"PyPI","package":"authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"1.6.0"},{"fixed":"1.9.0"}]}],"versions":["1.6.0","1.6.1","1.6.10","1.6.11","1.6.12","1.6.2","1.6.3","1.6.4","1.6.5","1.6.6","1.6.7","1.6.8","1.6.9","1.7.0","1.7.1","1.7.2","1.8.0"]},{"source":"osv","ecosystem":"PyPI","package":"authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"1.6.0"},{"fixed":"1.9.0"}]}],"versions":["1.6.0","1.6.1","1.6.10","1.6.11","1.6.12","1.6.2","1.6.3","1.6.4","1.6.5","1.6.6","1.6.7","1.6.8","1.6.9","1.7.0","1.7.1","1.7.2","1.8.0"]},{"source":"osv","ecosystem":"Ubuntu:Pro:22.04:LTS","package":"python-authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.15.4-1","0.15.5-1","0.15.5-1ubuntu0.1~esm1","0.15.5-1ubuntu0.1~esm2"]},{"source":"osv","ecosystem":"Ubuntu:Pro:24.04:LTS","package":"python-authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.2.1-1","1.3.0-1","1.3.0-1ubuntu0.1~esm1","1.3.0-1ubuntu0.1~esm2"]},{"source":"osv","ecosystem":"Ubuntu:26.10","package":"python-authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.6.7-1","1.7.2-1","1.7.2-2"]},{"source":"osv","ecosystem":"Ubuntu:Pro:26.04:LTS","package":"python-authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.6.0-1","1.6.5-1","1.6.6-1","1.6.7-1","1.6.7-1ubuntu0.1~esm1"]}],"products":[{"vendor":"authlib","product":"authlib"}],"references":[{"url":"https://github.com/authlib/authlib/security/advisories/GHSA-x4rv-c3wx-4cr8","tags":["x_refsource_CONFIRM","exploit","Exploit","Vendor Advisory","advisory","web","evidence","report"]},{"url":"https://github.com/authlib/authlib/pull/949","tags":["x_refsource_MISC","Patch","fix","web","report"]},{"url":"https://github.com/authlib/authlib/commit/6563f6f7a8c2f71d2f85ee7511d72a4fbae3acc0","tags":["x_refsource_MISC","Patch","fix","web","report"]},{"url":"https://github.com/authlib/authlib/commit/891e3f10ae5989176211f8be35dcc7cd40aa92c6","tags":["x_refsource_MISC","Patch","fix","web","report"]},{"url":"https://github.com/authlib/authlib/releases/tag/v1.9.0","tags":["x_refsource_MISC","Release Notes","web","advisory","report"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-114620","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-x4rv-c3wx-4cr8"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/114xxx/CVE-2026-114620.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-114620","tags":["advisory"]},{"url":"https://github.com/authlib/authlib","tags":["package"]},{"url":"https://ubuntu.com/security/CVE-2026-114620","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-114620","tags":["report"]}],"sources":{"cve":[{"id":"CVE-2026-114620","title":"Authlib: Unvalidated request_uri fetch in JWT-secured authorization request (JAR) leading to SSRF","description":"Authlib is a Python library which builds OAuth and OpenID Connect servers. Prior to 1.9.0, Authlib's RFC 9101 JWTAuthenticationRequest passes the unauthenticated, client-controlled request_uri parameter to the abstract get_request_object() hook before applying a destination or registration policy; the hook itself performs no network request, while the documentation demonstrates an unrestricted HTTP fetch. In deployments whose integrator implements that hook as an unrestricted outbound fetch, a remote attacker who knows a valid public client identifier can make the authorization server request internal, link-local, or otherwise unintended URLs and can distinguish fetch outcomes through different error responses and timing. Deployments whose custom hook already applies a secure allowlist, destination controls, and retrieval limits are not affected. The JWTAuthenticationRequest implementation in authlib/oauth2/rfc9101/authorization_server.py forwards request_uri to get_request_object() before an application-approved retrieval policy accepts the destination. This issue is fixed in version 1.9.0.","status":"published","published":"2026-10-22T21:34:11.909Z","modified":"2026-10-30T20:14:29.752Z","cvss":[{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-918"],"updated_at":"2026-11-02T13:40:01.309549Z"}],"euvd":[{"id":"EUVD-2026-108522","description":"Authlib is a Python library which builds OAuth and OpenID Connect servers. Prior to 1.9.0, Authlib's RFC 9101 JWTAuthenticationRequest passes the unauthenticated, client-controlled request_uri parameter to the abstract get_request_object() hook before applying a destination or registration policy; the hook itself performs no network request, while the documentation demonstrates an unrestricted HTTP fetch. In deployments whose integrator implements that hook as an unrestricted outbound fetch, a remote attacker who knows a valid public client identifier can make the authorization server request internal, link-local, or otherwise unintended URLs and can distinguish fetch outcomes through different error responses and timing. Deployments whose custom hook already applies a secure allowlist, destination controls, and retrieval limits are not affected. The JWTAuthenticationRequest implementation in authlib/oauth2/rfc9101/authorization_server.py forwards request_uri to get_request_object() before an application-approved retrieval policy accepts the destination. This issue is fixed in version 1.9.0.","status":"published","published":"2026-10-22T21:34:11Z","modified":"2026-10-30T20:14:29Z","cvss":[{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T16:59:01.965054Z"}],"ghsa":[{"id":"GHSA-x4rv-c3wx-4cr8","title":"Authlib: Unvalidated request_uri fetch in JWT-secured authorization request (JAR) leading to SSRF","description":"### Summary\n\nAuthlib's RFC 9101 (JAR) extension passes the attacker-controlled `request_uri` authorization parameter straight to the `get_request_object()` fetch hook with no validation, at a pre-authentication endpoint. Authlib already defines the pre-registration allowlist for this (`request_uris` client metadata and `require_request_uri_registration` server metadata) but the extension never consults it, and both docstrings instruct integrators to implement the hook as an unrestricted `requests.get(request_uri)`. Deployments following the documented pattern get an unauthenticated SSRF primitive. \n### Details\n\nVerified against authlib 1.7.2 and current `main`. All line numbers below are from `authlib/oauth2/rfc9101/authorization_server.py`.\n\nThe extension registers itself on the `before_get_authorization_grant` hook (lines 62-65), so it runs on every authorization request before any user or client authentication. `support_request_uri` defaults to `True` (line 58).\n\nThe only gate before the sink is that `client_id` names an existing client (lines 91-103). A `client_id` is a public, non-secret identifier.\n\nThe sink at line 159 forwards the raw parameter with no scheme, host, or allowlist check:\n\n```python\ndef _get_raw_request_object(self, request: OAuth2Request) -\u003e str:\n    if \"request_uri\" in request.payload.data:\n        raw_request_object = self.get_request_object(\n            request.payload.data[\"request_uri\"]\n        )\n        if not raw_request_object:\n            raise InvalidRequestUriError(state=request.payload.state)\n```\n\n`get_request_object` is abstract (line 213, `raise NotImplementedError()` at line 225), so authlib itself performs no outbound request. The defect is that authlib supplies unvalidated attacker input to that extension point and then documents the insecure implementation twice — in the class docstring (lines 34-38) and in the hook's own docstring (lines 218-223):\n\n```python\ndef get_request_object(self, request_uri: str):\n    try:\n        return requests.get(request_uri).text\n    except requests.Exception:\n        return None\n```\n\nTwo points sharpen this beyond a generic documentation issue:\n\n- **The allowlist already exists and is ignored.** Authlib validates `request_uris` as client metadata (`authlib/oauth2/rfc7591/claims.py:319`) and `require_request_uri_registration` as server metadata (`authlib/oauth2/rfc8414/models.py:263`). The `rfc9101` package contains zero references to either name, so the pre-registration model is never enforced at the point of use.\n- **RFC 9101 §10.4.1 asks for controls that are absent.** It states the server should \"a) check that the value of the `request_uri` parameter does not point to an unexpected location, b) check that the media type of the response is `application/oauth-authz-req+jwt`, c) implement a timeout for obtaining the content of `request_uri`, and d) not perform recursive GET on the `request_uri`.\" The extension implements none of these and the documented example implements none of them either.\n\nThe two failure paths are distinguishable, which supplies the inference channel for a blind SSRF: a falsy fetch result raises `InvalidRequestUriError` (`invalid_request_uri`, lines 162-163), while a successful fetch of non-JWT content raises `InvalidRequestObjectError` (`invalid_request_object`, line 186).\n\n### PoC\n\nThis demonstrates the library-level defect with no network egress required: it shows the unvalidated URI reaching the fetch hook pre-authentication.\n\n```bash\npython -m venv .venv \u0026\u0026 .venv/bin/pip install authlib\n```\n\n```python\nfrom authlib.oauth2.rfc6749.requests import BasicOAuth2Payload, OAuth2Request\nfrom authlib.oauth2.rfc9101 import JWTAuthenticationRequest\n\nreached = []\n\n\nclass Extension(JWTAuthenticationRequest):\n    def get_request_object(self, request_uri):\n        reached.append(request_uri)  # the docstring puts requests.get(request_uri) here\n        return None\n\n    def resolve_client_public_key(self, client):\n        return {\"keys\": []}\n\n    def get_server_metadata(self):\n        return {}\n\n    def get_client_require_signed_request_object(self, client):\n        return False\n\n\nclass AuthorizationServer:\n    def query_client(self, client_id):\n        return object()\n\n\nuri = \"http://169.254.169.254/latest/meta-data/\"\nrequest = OAuth2Request(\"GET\", \"https://as.example/authorize\")\nrequest.payload = BasicOAuth2Payload({\"client_id\": \"demo\", \"request_uri\": uri})\n\ntry:\n    Extension().parse_authorization_request(AuthorizationServer(), request)\nexcept Exception as exc:\n    print(\"error:\", type(exc).__name__)\n\nprint(\"fetch hook received:\", reached)\n```\n\nOutput on authlib 1.7.2:\n\n```text\nerror: InvalidRequestUriError\nfetch hook received: ['http://169.254.169.254/latest/meta-data/']\n```\n\nAgainst a deployment that used the docstring implementation, the equivalent unauthenticated request is:\n\n```text\nGET /authorize?response_type=code\u0026client_id=demo\u0026request_uri=http%3A%2F%2F169.254.169.254%2Flatest%2Fmeta-data%2F\n```\n\nReturning a fetched-but-non-JWT body instead of `None` in the snippet above yields `InvalidRequestObjectError` rather than `InvalidRequestUriError`, showing the oracle that distinguishes a reachable target from an unreachable one.\n\n### Impact\n\nServer-side request forgery, reachable without authentication by anyone who knows a valid `client_id`. In a deployment that implemented `get_request_object` as documented, an attacker can make the authorization server issue GET requests to arbitrary URLs: internal-only services, cloud metadata endpoints such as `169.254.169.254`, and side-effectful internal GET routes. The `invalid_request_uri` versus `invalid_request_object` split, together with response timing, provides an oracle for internal host and port discovery. RFC 9101 §10.4.1 additionally notes the DoS and recursive-lookup risks that the missing timeout and recursion checks leave open.\n\nResponse bodies are consumed as a JWT and never reflected, so this is a blind primitive rather than direct data exfiltration. Because the fetch executes in integrator code, deployments that already allowlist inside their override are unaffected — but authlib provides no hook to make that the default, ships `support_request_uri=True`, and its documentation steers integrators to the unsafe implementation, so the insecure outcome is the expected one.","status":"published","published":"2026-10-27T15:39:18Z","modified":"2026-10-27T15:39:20Z","cvss":[{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-918"],"updated_at":"2026-11-02T14:41:37.912288Z"}],"nvd":[{"id":"CVE-2026-114620","description":"Authlib is a Python library which builds OAuth and OpenID Connect servers. Prior to 1.9.0, Authlib's RFC 9101 JWTAuthenticationRequest passes the unauthenticated, client-controlled request_uri parameter to the abstract get_request_object() hook before applying a destination or registration policy; the hook itself performs no network request, while the documentation demonstrates an unrestricted HTTP fetch. In deployments whose integrator implements that hook as an unrestricted outbound fetch, a remote attacker who knows a valid public client identifier can make the authorization server request internal, link-local, or otherwise unintended URLs and can distinguish fetch outcomes through different error responses and timing. Deployments whose custom hook already applies a secure allowlist, destination controls, and retrieval limits are not affected. The JWTAuthenticationRequest implementation in authlib/oauth2/rfc9101/authorization_server.py forwards request_uri to get_request_object() before an application-approved retrieval policy accepts the destination. This issue is fixed in version 1.9.0.","status":"published","published":"2026-10-22T22:18:05.09Z","modified":"2026-10-30T21:16:50.87Z","cvss":[{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:N","level":"medium","assessor":"secondary"}],"cwes":["CWE-918"],"updated_at":"2026-11-02T14:13:02.724605Z"}],"osv":[{"id":"CVE-2026-114620","title":"Authlib: Unvalidated request_uri fetch in JWT-secured authorization request (JAR) leading to SSRF","description":"Authlib is a Python library which builds OAuth and OpenID Connect servers. Prior to 1.9.0, Authlib's RFC 9101 JWTAuthenticationRequest passes the unauthenticated, client-controlled request_uri parameter to the abstract get_request_object() hook before applying a destination or registration policy; the hook itself performs no network request, while the documentation demonstrates an unrestricted HTTP fetch. In deployments whose integrator implements that hook as an unrestricted outbound fetch, a remote attacker who knows a valid public client identifier can make the authorization server request internal, link-local, or otherwise unintended URLs and can distinguish fetch outcomes through different error responses and timing. Deployments whose custom hook already applies a secure allowlist, destination controls, and retrieval limits are not affected. The JWTAuthenticationRequest implementation in authlib/oauth2/rfc9101/authorization_server.py forwards request_uri to get_request_object() before an application-approved retrieval policy accepts the destination. This issue is fixed in version 1.9.0.","status":"published","published":"2026-10-22T21:34:11.909Z","modified":"2026-11-01T02:30:35.183437533Z","cvss":[{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-918"],"updated_at":"2026-11-02T15:44:56.310988Z"},{"id":"DEBIAN-CVE-2026-114620","description":"Authlib is a Python library which builds OAuth and OpenID Connect servers. Prior to 1.9.0, Authlib's RFC 9101 JWTAuthenticationRequest passes the unauthenticated, client-controlled request_uri parameter to the abstract get_request_object() hook before applying a destination or registration policy; the hook itself performs no network request, while the documentation demonstrates an unrestricted HTTP fetch. In deployments whose integrator implements that hook as an unrestricted outbound fetch, a remote attacker who knows a valid public client identifier can make the authorization server request internal, link-local, or otherwise unintended URLs and can distinguish fetch outcomes through different error responses and timing. Deployments whose custom hook already applies a secure allowlist, destination controls, and retrieval limits are not affected. The JWTAuthenticationRequest implementation in authlib/oauth2/rfc9101/authorization_server.py forwards request_uri to get_request_object() before an application-approved retrieval policy accepts the destination. This issue is fixed in version 1.9.0.","status":"published","published":"2026-10-22T22:18:05.09Z","modified":"2026-10-31T10:00:34.38763211Z","cvss":[{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T15:45:50.923612Z"},{"id":"GHSA-x4rv-c3wx-4cr8","title":"Authlib: Unvalidated request_uri fetch in JWT-secured authorization request (JAR) leading to SSRF","description":"### Summary\n\nAuthlib's RFC 9101 (JAR) extension passes the attacker-controlled `request_uri` authorization parameter straight to the `get_request_object()` fetch hook with no validation, at a pre-authentication endpoint. Authlib already defines the pre-registration allowlist for this (`request_uris` client metadata and `require_request_uri_registration` server metadata) but the extension never consults it, and both docstrings instruct integrators to implement the hook as an unrestricted `requests.get(request_uri)`. Deployments following the documented pattern get an unauthenticated SSRF primitive. \n### Details\n\nVerified against authlib 1.7.2 and current `main`. All line numbers below are from `authlib/oauth2/rfc9101/authorization_server.py`.\n\nThe extension registers itself on the `before_get_authorization_grant` hook (lines 62-65), so it runs on every authorization request before any user or client authentication. `support_request_uri` defaults to `True` (line 58).\n\nThe only gate before the sink is that `client_id` names an existing client (lines 91-103). A `client_id` is a public, non-secret identifier.\n\nThe sink at line 159 forwards the raw parameter with no scheme, host, or allowlist check:\n\n```python\ndef _get_raw_request_object(self, request: OAuth2Request) -\u003e str:\n    if \"request_uri\" in request.payload.data:\n        raw_request_object = self.get_request_object(\n            request.payload.data[\"request_uri\"]\n        )\n        if not raw_request_object:\n            raise InvalidRequestUriError(state=request.payload.state)\n```\n\n`get_request_object` is abstract (line 213, `raise NotImplementedError()` at line 225), so authlib itself performs no outbound request. The defect is that authlib supplies unvalidated attacker input to that extension point and then documents the insecure implementation twice — in the class docstring (lines 34-38) and in the hook's own docstring (lines 218-223):\n\n```python\ndef get_request_object(self, request_uri: str):\n    try:\n        return requests.get(request_uri).text\n    except requests.Exception:\n        return None\n```\n\nTwo points sharpen this beyond a generic documentation issue:\n\n- **The allowlist already exists and is ignored.** Authlib validates `request_uris` as client metadata (`authlib/oauth2/rfc7591/claims.py:319`) and `require_request_uri_registration` as server metadata (`authlib/oauth2/rfc8414/models.py:263`). The `rfc9101` package contains zero references to either name, so the pre-registration model is never enforced at the point of use.\n- **RFC 9101 §10.4.1 asks for controls that are absent.** It states the server should \"a) check that the value of the `request_uri` parameter does not point to an unexpected location, b) check that the media type of the response is `application/oauth-authz-req+jwt`, c) implement a timeout for obtaining the content of `request_uri`, and d) not perform recursive GET on the `request_uri`.\" The extension implements none of these and the documented example implements none of them either.\n\nThe two failure paths are distinguishable, which supplies the inference channel for a blind SSRF: a falsy fetch result raises `InvalidRequestUriError` (`invalid_request_uri`, lines 162-163), while a successful fetch of non-JWT content raises `InvalidRequestObjectError` (`invalid_request_object`, line 186).\n\n### PoC\n\nThis demonstrates the library-level defect with no network egress required: it shows the unvalidated URI reaching the fetch hook pre-authentication.\n\n```bash\npython -m venv .venv \u0026\u0026 .venv/bin/pip install authlib\n```\n\n```python\nfrom authlib.oauth2.rfc6749.requests import BasicOAuth2Payload, OAuth2Request\nfrom authlib.oauth2.rfc9101 import JWTAuthenticationRequest\n\nreached = []\n\n\nclass Extension(JWTAuthenticationRequest):\n    def get_request_object(self, request_uri):\n        reached.append(request_uri)  # the docstring puts requests.get(request_uri) here\n        return None\n\n    def resolve_client_public_key(self, client):\n        return {\"keys\": []}\n\n    def get_server_metadata(self):\n        return {}\n\n    def get_client_require_signed_request_object(self, client):\n        return False\n\n\nclass AuthorizationServer:\n    def query_client(self, client_id):\n        return object()\n\n\nuri = \"http://169.254.169.254/latest/meta-data/\"\nrequest = OAuth2Request(\"GET\", \"https://as.example/authorize\")\nrequest.payload = BasicOAuth2Payload({\"client_id\": \"demo\", \"request_uri\": uri})\n\ntry:\n    Extension().parse_authorization_request(AuthorizationServer(), request)\nexcept Exception as exc:\n    print(\"error:\", type(exc).__name__)\n\nprint(\"fetch hook received:\", reached)\n```\n\nOutput on authlib 1.7.2:\n\n```text\nerror: InvalidRequestUriError\nfetch hook received: ['http://169.254.169.254/latest/meta-data/']\n```\n\nAgainst a deployment that used the docstring implementation, the equivalent unauthenticated request is:\n\n```text\nGET /authorize?response_type=code\u0026client_id=demo\u0026request_uri=http%3A%2F%2F169.254.169.254%2Flatest%2Fmeta-data%2F\n```\n\nReturning a fetched-but-non-JWT body instead of `None` in the snippet above yields `InvalidRequestObjectError` rather than `InvalidRequestUriError`, showing the oracle that distinguishes a reachable target from an unreachable one.\n\n### Impact\n\nServer-side request forgery, reachable without authentication by anyone who knows a valid `client_id`. In a deployment that implemented `get_request_object` as documented, an attacker can make the authorization server issue GET requests to arbitrary URLs: internal-only services, cloud metadata endpoints such as `169.254.169.254`, and side-effectful internal GET routes. The `invalid_request_uri` versus `invalid_request_object` split, together with response timing, provides an oracle for internal host and port discovery. RFC 9101 §10.4.1 additionally notes the DoS and recursive-lookup risks that the missing timeout and recursion checks leave open.\n\nResponse bodies are consumed as a JWT and never reflected, so this is a blind primitive rather than direct data exfiltration. Because the fetch executes in integrator code, deployments that already allowlist inside their override are unaffected — but authlib provides no hook to make that the default, ships `support_request_uri=True`, and its documentation steers integrators to the unsafe implementation, so the insecure outcome is the expected one.","status":"published","published":"2026-10-27T15:39:18Z","modified":"2026-10-30T10:10:50.503885942Z","cvss":[{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-918"],"updated_at":"2026-11-02T15:46:59.484835Z"},{"id":"PYSEC-2026-4360","description":"Authlib is a Python library which builds OAuth and OpenID Connect servers. Prior to 1.9.0, Authlib's RFC 9101 JWTAuthenticationRequest passes the unauthenticated, client-controlled request_uri parameter to the abstract get_request_object() hook before applying a destination or registration policy; the hook itself performs no network request, while the documentation demonstrates an unrestricted HTTP fetch. In deployments whose integrator implements that hook as an unrestricted outbound fetch, a remote attacker who knows a valid public client identifier can make the authorization server request internal, link-local, or otherwise unintended URLs and can distinguish fetch outcomes through different error responses and timing. Deployments whose custom hook already applies a secure allowlist, destination controls, and retrieval limits are not affected. The JWTAuthenticationRequest implementation in authlib/oauth2/rfc9101/authorization_server.py forwards request_uri to get_request_object() before an application-approved retrieval policy accepts the destination. This issue is fixed in version 1.9.0.","status":"published","published":"2026-10-22T22:18:05.09Z","modified":"2026-10-30T10:00:02.215927425Z","cvss":[{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T15:52:57.671918Z"},{"id":"UBUNTU-CVE-2026-114620","description":"Authlib is a Python library which builds OAuth and OpenID Connect servers. Prior to 1.9.0, Authlib's RFC 9101 JWTAuthenticationRequest passes the unauthenticated, client-controlled request_uri parameter to the abstract get_request_object() hook before applying a destination or registration policy; the hook itself performs no network request, while the documentation demonstrates an unrestricted HTTP fetch. In deployments whose integrator implements that hook as an unrestricted outbound fetch, a remote attacker who knows a valid public client identifier can make the authorization server request internal, link-local, or otherwise unintended URLs and can distinguish fetch outcomes through different error responses and timing. Deployments whose custom hook already applies a secure allowlist, destination controls, and retrieval limits are not affected. The JWTAuthenticationRequest implementation in authlib/oauth2/rfc9101/authorization_server.py forwards request_uri to get_request_object() before an application-approved retrieval policy accepts the destination. This issue is fixed in version 1.9.0.","status":"published","published":"2026-10-22T22:18:00Z","modified":"2026-10-28T17:50:37Z","cvss":[{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T16:55:59.591851Z"}]}}
