Skip to main content
CVE-2026-49264VulnerabilityPublished Updated

RevocationEndpoint · Oauthlib : Unsafe JSONP callback injection

Affects vdirsyncerSource GCVE Database

0
Compare

Not known to be exploited · CVSS 6.1 Medium : upgrade to 0.21.0_1 when convenient.

Data as of 10 Nov 2026, 23:34 UTC

CVSS 3.1 base score
6.1Medium
How severe the flaw is on the 0–10 CVSS scale.
Patch
Available – fixed in 0.21.0_1
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N

Exploitation evidence

  1. Disclosed: evidence found.

    CVE published 30 Sep 2026

  2. Detectable: no evidence on record.

    No public nuclei template indexed

  3. Weaponized: no evidence on record.

    No public exploit code indexed in Metasploit or Exploit-DB

  4. Exploited in the wild: no evidence on record.

    Not in CISA KEV and no source reports exploitation

  5. Observed by OffSeq Mirage: no evidence on record.

    No attack traffic recorded by OffSeq Mirage sensors

A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), the Metasploit, Exploit-DB and nuclei-templates index as of 10 Nov 2026.

What to do

Upgrade vdirsyncer to 0.21.0_1.

Affected software

ProductAffectedFixed in
Homebrewvdirsyncerpkg:brew/vdirsyncerOther threatsAffected
  • >=0.16.0 <0.21.0_1
Fixed in
  • 0.21.0_1

Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.

Description

### Summary When `enable_jsonp=True`, oauthlib's `RevocationEndpoint` reflects the user-supplied `callback` parameter directly into JavaScript response bodies on both success and error paths without validating that it is a legal JSONP callback name. This allows arbitrary JavaScript response generation instead of a restricted function call, making the documented JSONP revocation feature unsafe for browser-based JSONP consumption when attackers can influence `callback`. ### Details The issue is in `oauthlib/oauth2/rfc6749/endpoints/revocation.py`. When `enable_jsonp=True` is passed to the `RevocationEndpoint` constructor, two code paths wrap the response body using the user-supplied `request.callback` parameter: ```python # Error response path (line 72-73): if self.enable_jsonp and request.callback: response_body = '{}({});'.format(request.callback, response_body) # Success response path (line 81-82): if self.enable_jsonp and request.callback: response_body = request.callback + '();' ``` The `request.callback` value comes from HTTP request parameters, either the query string or POST body, via the `Request` class in `oauthlib/common.py` (lines 394-395), which merges query and body parameters into `_params`. Any value the client sends as `callback` is used verbatim. There is no validation of any kind on the callback parameter: - No check that it is a valid JavaScript identifier - No whitelist of allowed characters - No escaping or sanitization A safe JSONP implementation must validate that the callback is a legal JavaScript function name (e.g., matching `^[a-zA-Z_$][a-zA-Z0-9_$.]*$`). Without this, the attacker controls the JavaScript code returned in the response body. This is the standard mitigation used by jQuery, Express.js, Google APIs, and other major JSONP implementations. **How the injection works:** An attacker sends a revocation request with a crafted callback value: ``` POST /revoke HTTP/1.1 Content-Type: application/x-www-form-urlencoded token=anything&callback=alert(document.cookie)// ``` The server responds with: ``` alert(document.cookie)//({"error": "invalid_client"}); ``` This is syntactically valid JavaScript. `alert(document.cookie)` executes as a statement, and `//` comments out the rest of the line. The attacker has full control over the code that appears before the `//`. **Execution context and attack surface:** JSONP works by having a browser load a remote script via `<script src="...">`. The loaded script executes in the origin of the **including page**, not the remote server. This means a `<script src="https://auth.example.com/revoke?token=x&callback=payload">` tag on `evil.com` would execute the payload in `evil.com`'s context, not the auth server's context. The impact comes from client-side integrations that consume this endpoint as trusted JSONP. If a legitimate OAuth client includes `<script src>` pointed at the revocation endpoint and an attacker can influence the callback value (e.g., via query parameter injection, a reflected value, or a man-in-the-middle downgrade), the attacker controls what code runs in the client application's origin. The authorization server becomes a source of attacker-controlled JavaScript that client applications trust and execute. Note: the `enable_jsonp` parameter defaults to `False`, so only deployments that explicitly opt in are affected. However, this is a supported, documented library feature, not a debug flag. The client-side library includes `prepare_token_revocation_request()` with an explicit `callback` parameter (`oauthlib/oauth2/rfc6749/parameters.py`, line 174), and the documentation shows JSONP revocation as a use case (`oauthlib/oauth2/rfc6749/clients/base.py`, lines 351-358). The existing test suite tests JSONP callback reflection with a benign value (`test_revocation_endpoint.py`, lines 91-101) but does not test for injection. Deployments that enable JSONP typically do so to support older browsers or cross-origin revocation from JavaScript clients, these are exactly the environments most sensitive to script injection. ### PoC **Requirements:** Python 3.x with oauthlib installed (`pip install oauthlib`). ```python from oauthlib.oauth2.rfc6749.endpoints.revocation import RevocationEndpoint from oauthlib.oauth2.rfc6749.request_validator import RequestValidator class FailValidator(RequestValidator): """Auth fails -> triggers error response path (lines 72-73).""" def client_authentication_required(self, request, *a, **kw): return True def authenticate_client(self, request, *a, **kw): return False def authenticate_client_id(self, client_id, request, *a, **kw): return False class PassValidator(RequestValidator): """Auth passes -> triggers success response path (lines 81-82).""" def client_authentication_required(self, request, *a, **kw): return False def authenticate_client_id(self, client_id, request, *a, **kw): return True def revoke_token(self, token, token_type_hint, request,

References

Community reviews

Loading reviews…