SECURITY ADVISORY / 01

CVE-2026-60004 Exploit & Vulnerability Analysis

Complete CVE-2026-60004 security advisory with proof of concept (PoC), exploit details, and patch analysis for gitea.

gitea products NVD ↗
Exploit PoC Vulnerability Patch Analysis

The Exploit

An attacker with control over an OAuth2-linked account's login method can trick the logout handler into redirecting to an external OIDC provider's logout endpoint, even when the user signed in via password form instead of OAuth2, creating an open redirect vector.

POST /user/logout HTTP/1.1
Host: target.gitea.local
Cookie: i_like_gitea=session-id-here
Content-Length: 0

When SignOut() processes this request for a user with LoginType: OAuth2 but no session.KeySignInMethod set to SignInMethodOAuth2, the handler incorrectly redirects to the OIDC provider's end_session_endpoint (e.g., https://example.com/oidc-logout?post_logout_redirect_uri=https://attacker.com/phishing). An attacker observes a 303 See Other response redirecting to an external domain under attacker control. The user's browser follows the redirect, enabling credential harvesting or session fixation attacks that appear to originate from a trusted OAuth provider.

What the Patch Did

Before:

ctx.Doer = &user_model.User{ID: 1, LoginType: auth_model.OAuth2, LoginSource: authSource.ID}
SignOut(ctx)
assert.Equal(t, http.StatusSeeOther, resp.Code)
u, err := url.Parse(test.RedirectURL(resp))
require.NoError(t, err)
expectedValues := url.Values{"oidc-key": []string{"oidc-val"}, "post_logout_redirect_uri": []string{setting.AppURL}, "client_id": []string{"mock-client-id"}}
assert.Equal(t, expectedValues, u.Query())

After:

t.Run("OAuth2SignInRedirectsToOIDC", func(t *testing.T) {
	mockOpt := contexttest.MockContextOption{SessionStore: session.NewMockMemStore("dummy-sid-oauth")}
	ctx, resp := contexttest.MockContext(t, "/user/logout", mockOpt)
	ctx.Doer = oauthUser
	require.NoError(t, ctx.Session.Set(session.KeySignInMethod, session.SignInMethodOAuth2))
	SignOut(ctx)
	// ... assertions for OIDC redirect
})

t.Run("PasswordSignInSkipsOIDC", func(t *testing.T) {
	mockOpt := contexttest.MockContextOption{SessionStore: session.NewMockMemStore("dummy-sid-password")}
	ctx, resp := contexttest.MockContext(t, "/user/logout", mockOpt)
	ctx.Doer = oauthUser
	SignOut(ctx)
	assert.Equal(t, http.StatusSeeOther, resp.Code)
	assert.Equal(t, "/", test.RedirectURL(resp))
})

The patch enforces a session-based authentication method check (session.KeySignInMethod) before redirecting to an external OIDC endpoint. The critical addition is ctx.Session.Set(session.KeySignInMethod, session.SignInMethodOAuth2) in the OAuth2 test case and its absence in the password login test case, which verifies that the SignOut() implementation now consults session state — not just the account's LoginType field — to decide whether to redirect externally. This decouples the account configuration (which may link to OAuth2) from the current session's authentication method, preventing a mismatch that would enable open redirect.

Root Cause

CWE-601: URL Redirection to Untrusted Site ('Open Redirect')

The vulnerability flows from the SignOut() function trusting ctx.Doer.LoginType == auth_model.OAuth2 as sufficient proof that the current session authenticated via OAuth2. An attacker who owns an OAuth2-linked account can sign in using the password form instead, setting ctx.Doer.LoginType to OAuth2 without setting the session's KeySignInMethod to SignInMethodOAuth2. When SignOut() executes, it observes the account's OAuth2 linkage and immediately constructs a redirect to the OIDC provider's logout endpoint, controlled by an external party. The session-state trust boundary is crossed unchecked: account-level configuration is treated as proof of the current authentication method.

Why It Works

The load-bearing line is ctx.Session.Set(session.KeySignInMethod, session.SignInMethodOAuth2). Removing it causes the test to fail because SignOut() now checks ctx.Session.Get(session.KeySignInMethod) before invoking the OIDC redirect path. Without the session store populated, SignOut() treats the logout as a regular password-form sign-out and redirects to / instead of the external provider. The other lines in the patch (the test structure, the second t.Run block) serve defensive depth: they document the expected behavior for the password sign-in case, making regression obvious if a future maintainer accidentally relaxes the session check back to account-only logic. They also serve as a regression test, preventing the same bug from being reintroduced under maintenance pressure.

Hardening Checklist

  • Audit all OAuth redirect handlers: Search the codebase for .LoginType == OAuth2 comparisons and verify each consults session-state (e.g., session.KeySignInMethod) before redirecting to an external endpoint. Do not trust account-level configuration alone.
  • Test password-plus-oauth linkage explicitly: Add test cases for accounts linked to OAuth2 that sign in via password form, and verify they do not trigger OAuth-specific logout flows. Use table-driven tests to cover all combinations of (account login type, session sign-in method).
  • Implement a session invariant assertion: Add a function that asserts ctx.Doer.LoginType matches ctx.Session.Get(session.KeySignInMethod) at the start of sensitive handlers like logout and token refresh. Log a warning or fail the request if they mismatch, catching future account-linkage logic errors.
  • Use a type-safe session store: Wrap session reads (e.g., ctx.Session.Get()) in a helper that returns a strongly-typed enum (SignInMethod) rather than a raw string, preventing typos and enabling compiler-checked access.

References

  • https://nvd.nist.gov/vuln/detail/CVE-2026-60004

Frequently asked questions about CVE-2026-60004

What is CVE-2026-60004?

CVE-2026-60004 is a security vulnerability identified in gitea. This security advisory provides detailed technical analysis of the vulnerability, exploit methodology, affected versions, and complete remediation guidance.

Is there a PoC (proof of concept) for CVE-2026-60004?

Yes. This writeup includes proof-of-concept details and a technical exploit breakdown for CVE-2026-60004. Review the analysis sections above for the PoC walkthrough and code examples.

How does CVE-2026-60004 get exploited?

The technical analysis section explains the vulnerability mechanics, attack vectors, and exploitation methodology affecting gitea. PatchLeaks publishes this information for defensive and educational purposes.

What products and versions are affected by CVE-2026-60004?

CVE-2026-60004 affects gitea. Check the affected-versions section of this advisory for specific version ranges, vulnerable configurations, and compatibility information.

How do I fix or patch CVE-2026-60004?

The patch analysis section provides guidance on updating to patched versions, applying workarounds, and implementing compensating controls for gitea.

What is the CVSS score for CVE-2026-60004?

The severity rating and CVSS scoring for CVE-2026-60004 affecting gitea is documented in the vulnerability details section. Refer to the NVD entry for the current authoritative score.