diff --git a/internal/outpost/proxyv2/application/mode_common.go b/internal/outpost/proxyv2/application/mode_common.go index 87a8bb0ff5..239b226c1a 100644 --- a/internal/outpost/proxyv2/application/mode_common.go +++ b/internal/outpost/proxyv2/application/mode_common.go @@ -110,17 +110,6 @@ func (a *Application) getTraefikForwardUrl(r *http.Request) (*url.URL, error) { // getNginxForwardUrl See https://github.com/kubernetes/ingress-nginx/blob/main/rootfs/etc/nginx/template/nginx.tmpl func (a *Application) getNginxForwardUrl(r *http.Request) (*url.URL, error) { - ou := r.Header.Get("X-Original-URI") - if ou != "" { - // Turn this full URL into a relative URL - u := &url.URL{ - Host: "", - Scheme: "", - Path: ou, - } - a.log.WithField("url", u.String()).Info("building forward URL from X-Original-URI") - return u, nil - } h := r.Header.Get("X-Original-URL") if len(h) < 1 { return nil, errors.New("no forward URL found") diff --git a/internal/outpost/proxyv2/application/mode_forward_nginx_test.go b/internal/outpost/proxyv2/application/mode_forward_nginx_test.go index a07eeba4bf..5fa8cbf44f 100644 --- a/internal/outpost/proxyv2/application/mode_forward_nginx_test.go +++ b/internal/outpost/proxyv2/application/mode_forward_nginx_test.go @@ -5,10 +5,8 @@ import ( "net/http/httptest" "testing" - "github.com/google/uuid" "github.com/stretchr/testify/assert" "goauthentik.io/internal/outpost/proxyv2/constants" - "goauthentik.io/internal/outpost/proxyv2/types" api "goauthentik.io/packages/client-go" ) @@ -47,67 +45,6 @@ func TestForwardHandleNginx_Single_Headers(t *testing.T) { assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect]) } -func TestForwardHandleNginx_Single_URI(t *testing.T) { - a := newTestApplication() - req, _ := http.NewRequest("GET", "https://foo.bar/outpost.goauthentik.io/auth/nginx", nil) - req.Header.Set("X-Original-URI", "/app") - - rr := httptest.NewRecorder() - a.forwardHandleNginx(rr, req) - - assert.Equal(t, http.StatusUnauthorized, rr.Code) - - s, _ := a.sessions.Get(req, a.SessionName()) - assert.Equal(t, "/app", s.Values[constants.SessionRedirect]) -} - -func TestForwardHandleNginx_Single_Claims(t *testing.T) { - a := newTestApplication() - req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/nginx", nil) - req.Header.Set("X-Original-URI", "/") - - rr := httptest.NewRecorder() - a.forwardHandleNginx(rr, req) - - s, _ := a.sessions.Get(req, a.SessionName()) - s.ID = uuid.New().String() - s.Options.MaxAge = 86400 - s.Values[constants.SessionClaims] = types.Claims{ - Sub: "foo", - Proxy: &types.ProxyClaims{ - UserAttributes: map[string]any{ - "username": "foo", - "password": "bar", - "additionalHeaders": map[string]any{ - "foo": "bar", - }, - }, - }, - } - err := a.sessions.Save(req, rr, s) - if err != nil { - panic(err) - } - - rr = httptest.NewRecorder() - a.forwardHandleNginx(rr, req) - - h := rr.Result().Header - - assert.Equal(t, []string{"Basic Zm9vOmJhcg=="}, h["Authorization"]) - assert.Equal(t, []string{"bar"}, h["Foo"]) - assert.Equal(t, []string{""}, h["User-Agent"]) - assert.Equal(t, []string{""}, h["X-Authentik-Email"]) - assert.Equal(t, []string{""}, h["X-Authentik-Groups"]) - assert.Equal(t, []string{""}, h["X-Authentik-Jwt"]) - assert.Equal(t, []string{""}, h["X-Authentik-Meta-App"]) - assert.Equal(t, []string{""}, h["X-Authentik-Meta-Jwks"]) - assert.Equal(t, []string{""}, h["X-Authentik-Meta-Outpost"]) - assert.Equal(t, []string{""}, h["X-Authentik-Name"]) - assert.Equal(t, []string{"foo"}, h["X-Authentik-Uid"]) - assert.Equal(t, []string{""}, h["X-Authentik-Username"]) -} - func TestForwardHandleNginx_Domain_Blank(t *testing.T) { a := newTestApplication() a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr() diff --git a/website/docs/security/cves/GHSA-5wcc-hf24-rf5h.md b/website/docs/security/cves/GHSA-5wcc-hf24-rf5h.md new file mode 100644 index 0000000000..a8e8e0e12a --- /dev/null +++ b/website/docs/security/cves/GHSA-5wcc-hf24-rf5h.md @@ -0,0 +1,33 @@ +# GHSA-5wcc-hf24-rf5h + +_Reported by [@bugbunny-research](https://github.com/bugbunny-research)_ + +## Unauthenticated Access via Client-Controlled X-Original-URI Header in Nginx Forward-Auth Mode + +### Summary + +In nginx forward-auth mode, the authentik outpost reads the forwarded request URL from a header that nginx does not set, but that a client can freely inject. By crafting this header to point at an internal outpost path, an unauthenticated attacker can cause the outpost to return HTTP 200 — causing nginx to forward the original request to the protected backend without authentication. + +### Patches + +authentik 2025.12.5 and 2026.2.3 fix this issue. + +### Impact + +This vulnerability only affects deployments using authentik's nginx forward-auth integration. Traefik, Caddy, and proxy mode deployments are not affected. + +In nginx forward-auth mode, the outpost builds the URL it evaluates from a header that nginx never sets but clients can freely inject. Because nginx's `auth_request` module forwards all client headers to the authentication subrequest, an attacker-supplied value reaches the outpost unmodified. + +The outpost unconditionally allows requests whose forwarded URL path begins with `/outpost.goauthentik.io/` (to permit internal endpoints such as the OAuth callback). An attacker can set the injected header to any path under that prefix, causing the outpost to return 200 and nginx to proxy the original request to the backend as if authenticated. + +The attack requires only a single HTTP header — no account, session, or prior knowledge of the target application. Any resource behind the nginx gateway is accessible: reads, writes, and deletes depending on what the backend exposes. The CVSS 3.1 score is 10.0 (Critical). + +### Workarounds + +Operators can mitigate this immediately at the nginx layer by explicitly clearing the `X-Original-URI` header in the nginx location block that proxies traffic to the outpost. This prevents the attacker-supplied value from reaching the outpost regardless of the application-level logic. Refer to the nginx documentation for `proxy_set_header` to clear individual headers. + +### For more information + +If you have any questions or comments about this advisory: + +- Email us at [security@goauthentik.io](mailto:security@goauthentik.io)