Skip to content

Commit a575f64

Browse files
committed
Avoid auth on cross-origin redirects
1 parent 1cb3999 commit a575f64

2 files changed

Lines changed: 100 additions & 0 deletions

File tree

github/github.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1981,6 +1981,20 @@ func setCredentialsAsHeaders(req *http.Request, id, secret string) *http.Request
19811981
return &convertedRequest
19821982
}
19831983

1984+
func isCrossOriginRedirect(req *http.Request) bool {
1985+
if req == nil || req.URL == nil || req.Response == nil || req.Response.Request == nil || req.Response.Request.URL == nil {
1986+
return false
1987+
}
1988+
return !sameRedirectOrigin(req.URL, req.Response.Request.URL)
1989+
}
1990+
1991+
func sameRedirectOrigin(a, b *url.URL) bool {
1992+
if a == nil || b == nil {
1993+
return false
1994+
}
1995+
return strings.EqualFold(a.Scheme, b.Scheme) && strings.EqualFold(a.Host, b.Host)
1996+
}
1997+
19841998
/*
19851999
UnauthenticatedRateLimitedTransport allows you to make unauthenticated calls
19862000
that need to use a higher rate limit associated with your OAuth application.
@@ -2021,6 +2035,10 @@ func (t *UnauthenticatedRateLimitedTransport) RoundTrip(req *http.Request) (*htt
20212035
return nil, errors.New("t.ClientSecret is empty")
20222036
}
20232037

2038+
if isCrossOriginRedirect(req) {
2039+
return t.transport().RoundTrip(req)
2040+
}
2041+
20242042
req2 := setCredentialsAsHeaders(req, t.ClientID, t.ClientSecret)
20252043
// Make the HTTP request.
20262044
return t.transport().RoundTrip(req2)
@@ -2055,6 +2073,10 @@ type BasicAuthTransport struct {
20552073

20562074
// RoundTrip implements the RoundTripper interface.
20572075
func (t *BasicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
2076+
if isCrossOriginRedirect(req) {
2077+
return t.transport().RoundTrip(req)
2078+
}
2079+
20582080
req2 := setCredentialsAsHeaders(req, t.Username, t.Password)
20592081
if t.OTP != "" {
20602082
req2.Header.Set(headerOTP, t.OTP)

github/github_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4296,6 +4296,42 @@ func TestUnauthenticatedRateLimitedTransport_transport(t *testing.T) {
42964296
}
42974297
}
42984298

4299+
func TestUnauthenticatedRateLimitedTransport_doesNotAuthorizeCrossOriginRedirect(t *testing.T) {
4300+
t.Parallel()
4301+
4302+
gotAuth := make(chan string, 1)
4303+
attacker := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
4304+
gotAuth <- r.Header.Get("Authorization")
4305+
w.WriteHeader(http.StatusOK)
4306+
_, _ = w.Write([]byte(`{}`))
4307+
}))
4308+
defer attacker.Close()
4309+
4310+
trusted := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
4311+
http.Redirect(w, r, attacker.URL+"/steal", http.StatusFound)
4312+
}))
4313+
defer trusted.Close()
4314+
4315+
tp := &UnauthenticatedRateLimitedTransport{
4316+
ClientID: "id",
4317+
ClientSecret: "secret",
4318+
}
4319+
c := mustNewClient(t, WithHTTPClient(tp.Client()), WithURLs(Ptr(trusted.URL+"/"), nil))
4320+
req, err := c.NewRequest(t.Context(), "GET", "anything", nil)
4321+
if err != nil {
4322+
t.Fatalf("NewRequest returned unexpected error: %v", err)
4323+
}
4324+
resp, err := c.Do(req, nil)
4325+
if err != nil {
4326+
t.Fatalf("Do returned unexpected error: %v", err)
4327+
}
4328+
resp.Body.Close()
4329+
4330+
if got := <-gotAuth; got != "" {
4331+
t.Errorf("Authorization on cross-origin redirect = %q, want empty", got)
4332+
}
4333+
}
4334+
42994335
func TestBasicAuthTransport(t *testing.T) {
43004336
t.Parallel()
43014337
client, mux, _ := setup(t)
@@ -4330,6 +4366,48 @@ func TestBasicAuthTransport(t *testing.T) {
43304366
assertNilError(t, err)
43314367
}
43324368

4369+
func TestBasicAuthTransport_doesNotAuthorizeCrossOriginRedirect(t *testing.T) {
4370+
t.Parallel()
4371+
4372+
gotAuth := make(chan string, 1)
4373+
gotOTP := make(chan string, 1)
4374+
attacker := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
4375+
gotAuth <- r.Header.Get("Authorization")
4376+
gotOTP <- r.Header.Get(headerOTP)
4377+
w.WriteHeader(http.StatusOK)
4378+
_, _ = w.Write([]byte(`{}`))
4379+
}))
4380+
defer attacker.Close()
4381+
4382+
trusted := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
4383+
http.Redirect(w, r, attacker.URL+"/steal", http.StatusFound)
4384+
}))
4385+
defer trusted.Close()
4386+
4387+
tp := &BasicAuthTransport{
4388+
Username: "u",
4389+
Password: "p",
4390+
OTP: "123456",
4391+
}
4392+
c := mustNewClient(t, WithHTTPClient(tp.Client()), WithURLs(Ptr(trusted.URL+"/"), nil))
4393+
req, err := c.NewRequest(t.Context(), "GET", "anything", nil)
4394+
if err != nil {
4395+
t.Fatalf("NewRequest returned unexpected error: %v", err)
4396+
}
4397+
resp, err := c.Do(req, nil)
4398+
if err != nil {
4399+
t.Fatalf("Do returned unexpected error: %v", err)
4400+
}
4401+
resp.Body.Close()
4402+
4403+
if got := <-gotAuth; got != "" {
4404+
t.Errorf("Authorization on cross-origin redirect = %q, want empty", got)
4405+
}
4406+
if got := <-gotOTP; got != "" {
4407+
t.Errorf("OTP on cross-origin redirect = %q, want empty", got)
4408+
}
4409+
}
4410+
43334411
func TestBasicAuthTransport_transport(t *testing.T) {
43344412
t.Parallel()
43354413
// default transport

0 commit comments

Comments
 (0)