From 5a98ed32956ddb721237ecd0850aec342c689e4a Mon Sep 17 00:00:00 2001 From: shriza1991 Date: Sat, 18 Jul 2026 23:13:46 +0530 Subject: [PATCH] oauth: reject redirects for token endpoint requests --- internal/oauth/flow.go | 9 ++++- internal/oauth/flow_test.go | 73 +++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/internal/oauth/flow.go b/internal/oauth/flow.go index 0e6510c23..1e4521023 100644 --- a/internal/oauth/flow.go +++ b/internal/oauth/flow.go @@ -163,6 +163,9 @@ func PostToken(ctx context.Context, client *http.Client, tokenEndpoint string, f if client == nil { client = http.DefaultClient } + + tokenClient := *client + tokenClient.CheckRedirect = checkTokenRedirect if now == nil { now = time.Now } @@ -173,7 +176,7 @@ func PostToken(ctx context.Context, client *http.Client, tokenEndpoint string, f request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.Header.Set("Accept", "application/json") - response, err := client.Do(request) + response, err := tokenClient.Do(request) if err != nil { return Token{}, fmt.Errorf("oauth: token request failed: %w", err) } @@ -221,3 +224,7 @@ func PostToken(ctx context.Context, client *http.Client, tokenEndpoint string, f } return token, nil } + +func checkTokenRedirect(_ *http.Request, _ []*http.Request) error { + return http.ErrUseLastResponse +} diff --git a/internal/oauth/flow_test.go b/internal/oauth/flow_test.go index 917aea9ce..4f82096fb 100644 --- a/internal/oauth/flow_test.go +++ b/internal/oauth/flow_test.go @@ -188,6 +188,79 @@ func TestPostTokenRefusesInsecureEndpoint(t *testing.T) { t.Fatalf("err = %v, want ErrInsecureTokenEndpoint", err) } } +func TestExchangeCodeRejects307Redirect(t *testing.T) { + var attackerHit bool + + attacker := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + attackerHit = true + t.Errorf("redirect target received unexpected request") + })) + defer attacker.Close() + + redirect := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, attacker.URL, http.StatusTemporaryRedirect) + })) + defer redirect.Close() + + cfg := Config{ + ClientID: "c", + TokenEndpoint: redirect.URL, + } + + _, err := ExchangeCode( + context.Background(), + redirect.Client(), + cfg, + "code", + "verifier", + "http://127.0.0.1/cb", + nil, + ) + + if err == nil { + t.Fatal("expected redirect error") + } + + if attackerHit { + t.Fatal("redirect target received credential-bearing request") + } +} + +func TestRefreshRejects308Redirect(t *testing.T) { + var attackerHit bool + + attacker := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + attackerHit = true + t.Errorf("redirect target received unexpected request") + })) + defer attacker.Close() + + redirect := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, attacker.URL, http.StatusPermanentRedirect) + })) + defer redirect.Close() + + cfg := Config{ + ClientID: "c", + TokenEndpoint: redirect.URL, + } + + _, err := Refresh( + context.Background(), + redirect.Client(), + cfg, + Token{RefreshToken: "refresh-token"}, + nil, + ) + + if err == nil { + t.Fatal("expected redirect error") + } + + if attackerHit { + t.Fatal("redirect target received credential-bearing request") + } +} func TestRefreshNoToken(t *testing.T) { _, err := Refresh(context.Background(), http.DefaultClient, Config{TokenEndpoint: "https://a/token"}, Token{}, nil)