Skip to content

Commit b69cbb3

Browse files
authored
bugfix: Not providing any token in requests results in wrong error message (#149)
* Fix wrong error being reported when token missing in request * Remove a condition that never becomes true * Add myself to the list of AUTHORS assuming this is good style * Fix minor style issue
1 parent c61da38 commit b69cbb3

File tree

4 files changed

+62
-8
lines changed

4 files changed

+62
-8
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Please keep the list sorted.
33

44
adiabatic <[email protected]>
5+
Florian D. Loch <[email protected]>
56
Google LLC (https://opensource.google.com)
67
jamesgroat <[email protected]>
78
Joshua Carp <[email protected]>

csrf.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -274,16 +274,22 @@ func (cs *csrf) ServeHTTP(w http.ResponseWriter, r *http.Request) {
274274
}
275275
}
276276

277-
// If the token returned from the session store is nil for non-idempotent
278-
// ("unsafe") methods, call the error handler.
279-
if realToken == nil {
277+
// Retrieve the combined token (pad + masked) token...
278+
maskedToken, err := cs.requestToken(r)
279+
if err != nil {
280+
r = envError(r, ErrBadToken)
281+
cs.opts.ErrorHandler.ServeHTTP(w, r)
282+
return
283+
}
284+
285+
if maskedToken == nil {
280286
r = envError(r, ErrNoToken)
281287
cs.opts.ErrorHandler.ServeHTTP(w, r)
282288
return
283289
}
284290

285-
// Retrieve the combined token (pad + masked) token and unmask it.
286-
requestToken := unmask(cs.requestToken(r))
291+
// ... and unmask it.
292+
requestToken := unmask(maskedToken)
287293

288294
// Compare the request token against the real token
289295
if !compareTokens(requestToken, realToken) {

csrf_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,48 @@ func TestWithReferer(t *testing.T) {
374374
}
375375
}
376376

377+
// Requests without a token should fail with ErrNoToken.
378+
func TestNoTokenProvided(t *testing.T) {
379+
var finalErr error
380+
381+
s := http.NewServeMux()
382+
p := Protect(testKey, ErrorHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
383+
finalErr = FailureReason(r)
384+
})))(s)
385+
386+
var token string
387+
s.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
388+
token = Token(r)
389+
}))
390+
391+
// Obtain a CSRF cookie via a GET request.
392+
r, err := http.NewRequest("GET", "http://www.gorillatoolkit.org/", nil)
393+
if err != nil {
394+
t.Fatal(err)
395+
}
396+
397+
rr := httptest.NewRecorder()
398+
p.ServeHTTP(rr, r)
399+
400+
// POST the token back in the header.
401+
r, err = http.NewRequest("POST", "http://www.gorillatoolkit.org/", nil)
402+
if err != nil {
403+
t.Fatal(err)
404+
}
405+
406+
setCookie(rr, r)
407+
// By accident we use the wrong header name for the token...
408+
r.Header.Set("X-CSRF-nekot", token)
409+
r.Header.Set("Referer", "http://www.gorillatoolkit.org/")
410+
411+
rr = httptest.NewRecorder()
412+
p.ServeHTTP(rr, r)
413+
414+
if finalErr != nil && finalErr != ErrNoToken {
415+
t.Fatalf("middleware failed to return correct error: got '%v' want '%v'", finalErr, ErrNoToken)
416+
}
417+
}
418+
377419
func setCookie(rr *httptest.ResponseRecorder, r *http.Request) {
378420
r.Header.Set("Cookie", rr.Header().Get("Set-Cookie"))
379421
}

helpers.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func unmask(issued []byte) []byte {
105105

106106
// requestToken returns the issued token (pad + masked token) from the HTTP POST
107107
// body or HTTP header. It will return nil if the token fails to decode.
108-
func (cs *csrf) requestToken(r *http.Request) []byte {
108+
func (cs *csrf) requestToken(r *http.Request) ([]byte, error) {
109109
// 1. Check the HTTP header first.
110110
issued := r.Header.Get(cs.opts.RequestHeader)
111111

@@ -123,14 +123,19 @@ func (cs *csrf) requestToken(r *http.Request) []byte {
123123
}
124124
}
125125

126+
// Return nil (equivalent to empty byte slice) if no token was found
127+
if issued == "" {
128+
return nil, nil
129+
}
130+
126131
// Decode the "issued" (pad + masked) token sent in the request. Return a
127132
// nil byte slice on a decoding error (this will fail upstream).
128133
decoded, err := base64.StdEncoding.DecodeString(issued)
129134
if err != nil {
130-
return nil
135+
return nil, err
131136
}
132137

133-
return decoded
138+
return decoded, nil
134139
}
135140

136141
// generateRandomBytes returns securely generated random bytes.

0 commit comments

Comments
 (0)