CVE-2019-8400
Description
ORY Hydra before v1.0.0-rc.3+oryOS.9 had a reflected XSS vulnerability in the OAuth2 error fallback endpoint via the error_hint parameter.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
ORY Hydra before v1.0.0-rc.3+oryOS.9 had a reflected XSS vulnerability in the OAuth2 error fallback endpoint via the error_hint parameter.
Vulnerability
A reflected cross-site scripting (XSS) vulnerability existed in ORY Hydra versions before v1.0.0-rc.3+oryOS.9 [1][3]. The bug resided in the DefaultErrorHandler function within the OAuth2 fallback endpoint at /oauth2/fallbacks/error. User input from the error_hint query parameter was directly rendered into the HTTP response without proper sanitization or escaping, as the handler used fmt.Fprintf with a %s format verb [4]. The code change in commit 9b5bbd4 replaced this unsafe pattern with Go's html/template package, which automatically escapes output, thus mitigating the issue [4].
Exploitation
An attacker could craft a malicious URL containing a JavaScript payload in the error_hint parameter, for example by appending ?error_hint= to the fallback endpoint. No authentication was required to trigger the vulnerable endpoint, as the fallback URL was intended to be publicly reachable. The victim only needed to click on or visit the crafted link. The unsanitized input was reflected directly in the HTML response, causing the browser to execute the injected script [3][4].
Impact
Successful exploitation allowed an attacker to execute arbitrary JavaScript in the context of the victim's browser session on the ORY Hydra domain. This could lead to session hijacking, credential theft, or defacement of the OAuth2 error page. The attack did not provide server-side code execution or privilege escalation; it was confined to the client-side impact typical of reflected XSS [3][4].
Mitigation
The vulnerability was fixed in ORY Hydra version v1.0.0-rc.3+oryOS.9, released on 2018-12-06 [1][3]. The fix introduced HTML template escaping in the fallback endpoints via Go's html/template package [4]. Users should upgrade to this version or later. No workarounds were documented beyond ensuring the error URL environment variable (OAUTH2_ERROR_URL) is set to a custom, secure endpoint, which would bypass the vulnerable default handler entirely [4].
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/ory/hydraGo | < 1.4.8 | 1.4.8 |
Affected products
3- ghsa-coords2 versions
< 1.4.8+ 1 more
- (no CPE)range: < 1.4.8
- (no CPE)range: < 0.0.20260326T203309-150000.1.155.2
Patches
19b5bbd48a720oauth2: Use html templates in fallback endpoints (#1202)
4 files changed · +50 −90
go.mod+1 −1 modified@@ -42,7 +42,7 @@ require ( github.com/urfave/negroni v1.0.0 github.com/ziutek/mymysql v1.5.4 // indirect go.uber.org/atomic v1.3.2 // indirect - golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4 + golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 golang.org/x/net v0.0.0-20181029044818-c44066c5c816 // indirect golang.org/x/oauth2 v0.0.0-20181003184128-c57b0facaced gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
go.sum+2 −0 modified@@ -266,6 +266,8 @@ golang.org/x/crypto v0.0.0-20180830192347-182538f80094/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4 h1:Vk3wNqEZwyGyei9yq5ekj7frek2u7HUfffJ1/opblzc= golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180611182652-db08ff08e862 h1:JZi6BqOZ+iSgmLWe6llhGrNnEnK+YB/MRkStwnEfbqM= golang.org/x/net v0.0.0-20180611182652-db08ff08e862/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
main_test.go.bak+0 −78 removed@@ -1,78 +0,0 @@ -package main_test - -import ( - "fmt" - "github.com/jmoiron/sqlx" - "github.com/ory/fosite" - "github.com/ory/hydra/client" - "github.com/ory/hydra/consent" - "github.com/ory/hydra/jwk" - "github.com/ory/hydra/oauth2" - "github.com/ory/x/resilience" - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/require" - "os" - "testing" - "time" -) - -func connect(t *testing.T, driver, dsn string) (db *sqlx.DB) { - require.NoError(t, resilience.Retry( - logrus.New(), - time.Second * 5, - time.Minute * 5, - func() (err error) { - db, err = sqlx.Open(driver, dsn) - if err != nil { - return err - } - - return db.Ping() - }, - )) - return db -} - -func TestCreateSchemasAtRoot(t *testing.T) { - if testing.Short() { - t.Skip("Skipping database tests in short mode.") - return - } - - drivers := map[string]string{ - "TEST_DATABASE_POSTGRESQL": "postgres", - "TEST_DATABASE_MYSQL": "mysql", - } - urls := map[string]string{ - "TEST_DATABASE_POSTGRESQL": os.Getenv("TEST_DATABASE_POSTGRESQL"), - "TEST_DATABASE_MYSQL": os.Getenv("TEST_DATABASE_MYSQL"), - } - - for e, u := range urls { - if len(u) == 0 { - t.Fatalf("Environment variable %s is empty but it should point to a database. If you are running this test locally, make sure to run `make test` instead of `go test ./...`", e) - } - - driver := drivers[e] - t.Run(fmt.Sprintf("driver=%s", driver), func(t *testing.T) { - t.Logf("Connecting to database %s: %s", driver, u) - - db := connect(t, driver, u) - cm := &client.SQLManager{DB: db, Hasher: &fosite.BCrypt{}} - _, err := cm.CreateSchemas() - require.NoError(t, err) - - jm := &jwk.SQLManager{DB: db, Cipher: &jwk.AEAD{Key: []byte("11111111111111111111111111111111")}} - _, err = jm.CreateSchemas() - require.NoError(t, err) - - om := &oauth2.FositeSQLStore{Manager: cm, DB: db, L: logrus.New()} - _, err = om.CreateSchemas() - require.NoError(t, err) - - crm := consent.NewSQLManager(db, nil, om) - _, err = crm.CreateSchemas() - require.NoError(t, err) - }) - } -}
oauth2/handler_fallback_endpoints.go+47 −11 modified@@ -21,7 +21,7 @@ package oauth2 import ( - "fmt" + "html/template" "net/http" "github.com/julienschmidt/httprouter" @@ -31,7 +31,7 @@ func (h *Handler) DefaultConsentHandler(w http.ResponseWriter, r *http.Request, h.L.Warnln("It looks like no consent/login URL was set. All OAuth2 flows except client credentials will fail.") h.L.Warnln("A client requested the default login & consent URL, environment variable OAUTH2_CONSENT_URL or OAUTH2_LOGIN_URL or both are probably not set.") - w.Write([]byte(` + t, err := template.New("consent").Parse(` <html> <head> <title>Misconfigured consent/login URL</title> @@ -47,13 +47,22 @@ func (h *Handler) DefaultConsentHandler(w http.ResponseWriter, r *http.Request, </p> </body> </html> -`)) +`) + if err != nil { + h.H.WriteError(w, r, err) + return + } + + if err := t.Execute(w, nil); err != nil { + h.H.WriteError(w, r, err) + return + } } func (h *Handler) DefaultErrorHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { h.L.Warnln("A client requested the default error URL, environment variable OAUTH2_ERROR_URL is probably not set.") - fmt.Fprintf(w, ` + t, err := template.New("consent").Parse(` <html> <head> <title>An OAuth 2.0 Error Occurred</title> @@ -63,10 +72,10 @@ func (h *Handler) DefaultErrorHandler(w http.ResponseWriter, r *http.Request, _ The OAuth2 request resulted in an error. </h1> <ul> - <li>Error: %s</li> - <li>Description: %s</li> - <li>Hint: %s</li> - <li>Debug: %s</li> + <li>Error: {{ .Name }}</li> + <li>Description: {{ .Description }}</li> + <li>Hint: {{ .Hint }}</li> + <li>Debug: {{ .Debug }}</li> </ul> <p> You are seeing this default error page because the administrator has not set a dedicated error URL (environment variable <code>OAUTH2_ERROR_URL</code> is not set). @@ -75,13 +84,31 @@ func (h *Handler) DefaultErrorHandler(w http.ResponseWriter, r *http.Request, _ </p> </body> </html> -`, r.URL.Query().Get("error"), r.URL.Query().Get("error_description"), r.URL.Query().Get("error_hint"), r.URL.Query().Get("error_debug")) +`) + if err != nil { + h.H.WriteError(w, r, err) + return + } + + if err := t.Execute(w, struct { + Name string + Description string + Hint string + Debug string + }{ + Name: r.URL.Query().Get("error"), + Description: r.URL.Query().Get("error_description"), + Hint: r.URL.Query().Get("error_hint"), + Debug: r.URL.Query().Get("error_debug"), + }); err != nil { + h.H.WriteError(w, r, err) + return + } } func (h *Handler) DefaultLogoutHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { h.L.Warnln("A client requested the default logout URL, environment variable OAUTH2_LOGOUT_REDIRECT_URL is probably not set.") - - fmt.Fprintf(w, ` + t, err := template.New("consent").Parse(` <html> <head> <title>You logged out successfully</title> @@ -98,4 +125,13 @@ func (h *Handler) DefaultLogoutHandler(w http.ResponseWriter, r *http.Request, _ </body> </html> `) + if err != nil { + h.H.WriteError(w, r, err) + return + } + + if err := t.Execute(w, nil); err != nil { + h.H.WriteError(w, r, err) + return + } }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-7v6r-w4r6-mhchghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2019-8400ghsaADVISORY
- drive.google.com/file/d/1-25expUYVfK6vsiCmEabUCuelOP7aUDj/viewghsax_refsource_MISCWEB
- github.com/ory/hydra/blob/master/CHANGELOG.mdghsax_refsource_MISCWEB
- github.com/ory/hydra/commit/9b5bbd48a72096930af08402c5e07fce7dd770f3ghsax_refsource_MISCWEB
- hackerone.com/reports/456333ghsax_refsource_MISCWEB
- www.youtube.com/watchghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.