OpenBao has Reflected XSS in its OIDC authentication error message
Description
OpenBao is an open source identity-based secrets management system. Prior to version 2.5.2, OpenBao installations that have an OIDC/JWT authentication method enabled and a role with callback_mode=direct configured are vulnerable to XSS via the error_description parameter on the page for a failed authentication. This allows an attacker access to the token used in the Web UI by a victim. The error_description parameter has been replaced with a static error message in v2.5.2. The vulnerability can be mitigated by removing any roles with callback_mode set to direct.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/openbao/openbaoGo | < 0.0.0-20260325133417-6e2b2dd84f0e | 0.0.0-20260325133417-6e2b2dd84f0e |
Affected products
1Patches
16e2b2dd84f0eResolve GHSA-cpj3-3r2f-xj59 (#2709)
4 files changed · +73 −17
builtin/credential/jwt/html_responses.go+23 −11 modified@@ -3,7 +3,11 @@ package jwtauth -import "fmt" +import ( + "encoding/json" + "fmt" + "html" +) const successHTML = ` <!DOCTYPE html> @@ -159,7 +163,7 @@ const successHTML = ` <span class="icon"> <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> <path d="M13.307 1H11.5a.5.5 0 1 1 0-1h3a.499.499 0 0 1 .5.65V3.5a.5.5 0 1 1-1 0V1.72l-1.793 1.774a.5.5 0 0 1-.713-.701L13.307 1zM12 14V8a.5.5 0 1 1 1 0v6.5a.5.5 0 0 1-.5.5H.563a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 .5-.5H8a.5.5 0 0 1 0 1H1v12h11zM4 6a.5.5 0 0 1 0-1h3a.5.5 0 0 1 0 1H4zm0 2.5a.5.5 0 0 1 0-1h5a.5.5 0 0 1 0 1H4zM4 11a.5.5 0 1 1 0-1h5a.5.5 0 1 1 0 1H4z"/> - </svg> + </svg> </span> Check out the official OpenBao documentation </a> @@ -170,7 +174,7 @@ const successHTML = ` ` func errorHTML(summary, detail string) string { - const html = ` + const htmlTmpl = ` <!DOCTYPE html> <html lang="en" > @@ -234,7 +238,7 @@ hr { } .message.is-danger .message-title { color: #7f222c; - + } .message .message-body { border: 0; @@ -327,7 +331,7 @@ h1 + p { <div class="message is-danger"> <svg width="20" height="20" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <path d="M19 3c1.1 0 2 .9 2 2v14c0 1.1-.9 2-2 2H5c-1.1 0-2-.9-2-2V5c0-1.1.9-2 2-2h14zm-2 12.59L13.41 12 17 8.41 15.59 7 12 10.59 8.41 7 7 8.41 10.59 12 7 15.59 8.41 17 12 13.41 15.59 17 17 15.59z"></path> -</svg> +</svg> <div class="message-content"> <div class="message-title"> %s @@ -344,7 +348,7 @@ h1 + p { <span class="icon"> <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> <path d="M13.307 1H11.5a.5.5 0 1 1 0-1h3a.499.499 0 0 1 .5.65V3.5a.5.5 0 1 1-1 0V1.72l-1.793 1.774a.5.5 0 0 1-.713-.701L13.307 1zM12 14V8a.5.5 0 1 1 1 0v6.5a.5.5 0 0 1-.5.5H.563a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 .5-.5H8a.5.5 0 0 1 0 1H1v12h11zM4 6a.5.5 0 0 1 0-1h3a.5.5 0 0 1 0 1H4zm0 2.5a.5.5 0 0 1 0-1h5a.5.5 0 0 1 0 1H4zM4 11a.5.5 0 1 1 0-1h5a.5.5 0 1 1 0 1H4z"/> -</svg> +</svg> </span> Check out the official OpenBao documentation </a> @@ -354,11 +358,11 @@ h1 + p { </html> ` - return fmt.Sprintf(html, summary, detail) + return fmt.Sprintf(htmlTmpl, html.EscapeString(summary), html.EscapeString(detail)) } func formpostHTML(path, code, state string) string { - const html = ` + const htmlTmpl = ` <!DOCTYPE html> <html lang="en"> <head> @@ -509,17 +513,25 @@ func formpostHTML(path, code, state string) string { <span class="icon"> <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> <path d="M13.307 1H11.5a.5.5 0 1 1 0-1h3a.499.499 0 0 1 .5.65V3.5a.5.5 0 1 1-1 0V1.72l-1.793 1.774a.5.5 0 0 1-.713-.701L13.307 1zM12 14V8a.5.5 0 1 1 1 0v6.5a.5.5 0 0 1-.5.5H.563a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 .5-.5H8a.5.5 0 0 1 0 1H1v12h11zM4 6a.5.5 0 0 1 0-1h3a.5.5 0 0 1 0 1H4zm0 2.5a.5.5 0 0 1 0-1h5a.5.5 0 0 1 0 1H4zM4 11a.5.5 0 1 1 0-1h5a.5.5 0 1 1 0 1H4z"/> - </svg> + </svg> </span> Check out the official OpenBao documentation </a> </div> </div> <script> - window.opener.postMessage({ source: 'oidc-callback', path: "%s", code: "%s", state: "%s"}, window.origin); + window.opener.postMessage({ source: 'oidc-callback', path: %s, code: %s, state: %s}, window.origin); </script> </body> </html> ` - return fmt.Sprintf(html, path, code, state) + return fmt.Sprintf(htmlTmpl, jsStr(path), jsStr(code), jsStr(state)) +} + +// jsStr returns JSON-encoded string literal, safe to embed directly in a <script> block +// adds surrounding quotes +func jsStr(s string) string { + // json.Marshal on a string returns no errors + b, _ := json.Marshal(s) + return string(b) }
builtin/credential/jwt/path_oidc.go+40 −4 modified@@ -10,6 +10,7 @@ import ( "net/http" "net/url" "slices" + "strconv" "strings" "time" @@ -36,6 +37,17 @@ const ( noCode = "no_code" ) +// RFC 6749 §4.1.2.1 defined error codes for Authorization Code Grant error responses +const ( + oidcErrInvalidRequest = "invalid_request" + oidcErrUnauthorizedClient = "unauthorized_client" + oidcErrAccessDenied = "access_denied" + oidcErrUnsupportedResponseType = "unsupported_response_type" + oidcErrInvalidScope = "invalid_scope" + oidcErrServerError = "server_error" + oidcErrTemporarilyUnavailable = "temporarily_unavailable" +) + // oidcRequest represents a single OIDC authentication flow. It is created when // an authURL is requested. It is uniquely identified by a state, which is passed // throughout the multiple interactions needed to complete the flow. @@ -85,8 +97,17 @@ func pathOIDC(b *jwtAuthBackend) []*framework.Path { Type: framework.TypeString, Query: true, }, + "error": { + Type: framework.TypeString, + Query: true, + }, "error_description": { - Type: framework.TypeString, + Type: framework.TypeString, + Query: true, + }, + "error_uri": { + Type: framework.TypeString, + Query: true, }, }, @@ -266,9 +287,24 @@ func (b *jwtAuthBackend) pathCallback(ctx context.Context, req *logical.Request, deleteRequest = false } - errorDescription := d.Get("error_description").(string) - if errorDescription != "" { - return loginFailedResponse(useHttp, errorDescription), nil + // error parameter per OpenID Connect Core 1.0 spec + // If present, the login has failed + oidcError := strings.ToLower(strings.TrimSpace(d.Get("error").(string))) + if oidcError != "" { + // strconv.Quote - for log-safe string output. + b.Logger().Warn("OIDC callback received error from provider", + "error", strconv.Quote(oidcError), + "error_description", strconv.Quote(d.Get("error_description").(string)), + "error_uri", strconv.Quote(d.Get("error_uri").(string)), + ) + switch oidcError { + case oidcErrInvalidRequest, oidcErrUnauthorizedClient, oidcErrAccessDenied, + oidcErrUnsupportedResponseType, oidcErrInvalidScope, oidcErrServerError, + oidcErrTemporarilyUnavailable: + return loginFailedResponse(useHttp, oidcError), nil + default: + return loginFailedResponse(useHttp, "An unknown error occurred during OIDC authentication. Check server logs for details"), nil + } } clientNonce := d.Get("client_nonce").(string)
changelog/2709.txt+3 −0 added@@ -0,0 +1,3 @@ +```release-note:security +auth/jwt: prevent XSS via `error_description` parameter in `callback_mode=direct` auth methods. CVE-2026-33758. +```
website/content/api-docs/auth/jwt.mdx+7 −2 modified@@ -468,8 +468,13 @@ or direct callback modes, and is not used in device callback mode. - `client_nonce` `(string: <optional>)` - Optional client-provided nonce that must match the `client_nonce` value provided during the prior request to the [auth_url](jwt.mdx#oidc-authorization-url-request) API. -- `error_description` `(string: <optional>)` - Detailed description of an error if there was an error. - If present, will be included in the error message passed back to the requester. +- `error` `(string: <optional>)` - Error code from the OIDC provider per OpenID Connect Core 1.0 spec. + If present, indicates the authorization failed. Known RFC 6749 error codes are returned to the + requester; unrecognized codes result in a static failure message. The raw value is always logged server-side. +- `error_description` `(string: <optional>)` - Human-readable error description from OIDC provider. + Logged server-side only. +- `error_uri` `(string: <optional>)` - URI of a human-readable error page from the OIDC provider. + Logged server-side only. ### Sample request
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-cpj3-3r2f-xj59ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-33758ghsaADVISORY
- github.com/openbao/openbao/commit/6e2b2dd84f0e47cebc90d6e79609dd5274732662ghsax_refsource_MISCWEB
- github.com/openbao/openbao/pull/2709ghsax_refsource_MISCWEB
- github.com/openbao/openbao/releases/tag/v2.5.2ghsax_refsource_MISCWEB
- github.com/openbao/openbao/security/advisories/GHSA-cpj3-3r2f-xj59ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.