OAuth host poisoning enables cross-tenant account takeover
OAuth state signed an attacker-selected origin and the token endpoint trusted an unsigned session cookie
CVSS 3.1 base score
HighVector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
Summary
Source
WrapRequest()
app/pkg/web/request.go:33
Sink
OAuthToken()
app/handlers/oauth.go:183
An attacker could start OAuth with a poisoned Host, receive the victim's still-unused provider code at an attacker origin, and redeem it on a chosen tenant by matching an unsigned user_session_id cookie.
Root cause. SignInByOAuth derived its continuation from raw Host/X-Forwarded-Host and signed that origin into state. The callback redirected the provider code and identifier there, while OAuthToken neither revalidated the state nor consumed a server-side transaction and accepted a caller-set session cookie as the binding.
Remediation evidence. Fider responded to the disclosure and merged exact PR #1626 on 27 July 2026. It replaces request-host authority with canonical tenant origins and a signed handoff bound to the provider, origin, and browser session.
Severity
- Attack vectorAV
- Network
- Attack complexityAC
- Low
- Privileges requiredPR
- None
- User interactionUI
- Required
- ScopeS
- Unchanged
- ConfidentialityC
- High
- IntegrityI
- High
- AvailabilityA
- High
Metric values as published in the disclosure vector. Meters show how far each value raises exposure.
Source-to-sink trace
- Source · attacker-controlledapp/
pkg/ web/ request.go:33 WrapRequest()
- Step 01app/
pkg/ web/ request.go:33 An unauthenticated request selected the externally visible host from direct or forwarded request headers.
go host := request.Host if forwarded := request.Header.Get("X-Forwarded-Host"); forwarded != "" { host = forwarded } - Step 02app/
handlers/ oauth.go:SignInByOAuth() The OAuth start handler placed that attacker origin and a session identifier into signed state.
go redirect := c.BaseURL() state := jwt.EncodeOAuthState(redirect, c.SessionID()) - Step 03app/
handlers/ oauth.go:183 The tenant token endpoint redeemed the captured provider code after comparing only a caller-controlled cookie to the disclosed identifier.
go if c.Cookie("user_session_id") == state.Identifier { profile := provider.Exchange(code) SetAuthCookie(c, profile) } - Sinkapp/
handlers/ oauth.go:183 OAuthToken()
Impact
Reported impact
The attacker takes over the consenting victim's account in the selected tenant; if that victim is an administrator, the attacker gains tenant data export, settings, integration, user-management, and destructive content authority.
Attack surface
Built-in OAuth sign-in on multi-host Fider deployments that accepted arbitrary Host or untrusted forwarded-host values.
Preconditions
The attacker controls a public HTTPS origin and knows the target tenant host. A victim with an existing Fider account must approve the legitimate provider application; the attacker needs no OAuth client secret or Fider credential.
Attack path
- 1
Proxy the OAuth start request with the attacker origin as Host.
- 2
Have the victim complete normal provider authorization.
- 3
Capture the provider code and state identifier from Fider's redirect to the attacker origin.
- 4
Set
user_session_idto the captured identifier on the chosen victim tenant. - 5
Redeem the code at that tenant's OAuth token endpoint and receive the victim's Fider session.
Proof of concept
- 01
Environment setup
Use an isolated vulnerable multi-host Fider deployment, a victim test account, and an attacker-controlled HTTPS callback collector.
- 02
Target configuration
Enable a built-in global OAuth provider and allow the application to receive an arbitrary Host or client-supplied
X-Forwarded-Host. - 03
Exploit delivery
Start OAuth through the poisoned authority, complete consent as the victim, capture the redirected code/identifier, and replay them to the victim tenant with the matching unsigned cookie.
- 04
Expected response
The vulnerable tenant endpoint sets a valid Fider auth cookie for the victim account.
- 05
Outcome
PR #1626 accepts only a canonical tenant origin and a cryptographically bound, single-purpose handoff, so the captured values cannot be redirected or replayed into another tenant.
Remediation
Guidance
Upgrade to a Fider release containing PR #1626. Derive security redirects from canonical tenant configuration, trust forwarded headers only from configured proxies, and bind OAuth state to tenant, provider, origin, session, expiry, and single use.
redirect := c.BaseURL()state := jwt.EncodeOAuthState(redirect, c.SessionID())canonical := web.TenantBaseURL(c.Tenant())handoff := oauthTransactions.Create(tenantID, provider, sessionHash, canonical)state := jwt.EncodeOAuthState(handoff.ID)
Check the upstream record for the project's current remediation status.
Investigate the paths that matter in your codebase.
Winfunc can trace relevant code paths, preserve supporting evidence, and prepare remediation suggestions for engineering review within an agreed scope.

