@@ -35,14 +35,16 @@ var SignatureHeaders []string = []string{
35
35
}
36
36
37
37
type OAuthProxy struct {
38
- CookieSeed string
39
- CookieName string
40
- CookieDomain string
41
- CookieSecure bool
42
- CookieHttpOnly bool
43
- CookieExpire time.Duration
44
- CookieRefresh time.Duration
45
- Validator func (string ) bool
38
+ CookieSeed string
39
+ CookieName string
40
+ CSRFCookieName string
41
+ SessionCookieName string
42
+ CookieDomain string
43
+ CookieSecure bool
44
+ CookieHttpOnly bool
45
+ CookieExpire time.Duration
46
+ CookieRefresh time.Duration
47
+ Validator func (string ) bool
46
48
47
49
RobotsPath string
48
50
PingPath string
@@ -172,14 +174,16 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
172
174
}
173
175
174
176
return & OAuthProxy {
175
- CookieName : opts .CookieName ,
176
- CookieSeed : opts .CookieSecret ,
177
- CookieDomain : opts .CookieDomain ,
178
- CookieSecure : opts .CookieSecure ,
179
- CookieHttpOnly : opts .CookieHttpOnly ,
180
- CookieExpire : opts .CookieExpire ,
181
- CookieRefresh : opts .CookieRefresh ,
182
- Validator : validator ,
177
+ CookieName : opts .CookieName ,
178
+ CSRFCookieName : fmt .Sprintf ("%v_%v" , opts .CookieName , "csrf" ),
179
+ SessionCookieName : fmt .Sprintf ("%v_%v" , opts .CookieName , "session" ),
180
+ CookieSeed : opts .CookieSecret ,
181
+ CookieDomain : opts .CookieDomain ,
182
+ CookieSecure : opts .CookieSecure ,
183
+ CookieHttpOnly : opts .CookieHttpOnly ,
184
+ CookieExpire : opts .CookieExpire ,
185
+ CookieRefresh : opts .CookieRefresh ,
186
+ Validator : validator ,
183
187
184
188
RobotsPath : "/robots.txt" ,
185
189
PingPath : "/ping" ,
@@ -243,7 +247,18 @@ func (p *OAuthProxy) redeemCode(host, code string) (s *providers.SessionState, e
243
247
return
244
248
}
245
249
246
- func (p * OAuthProxy ) MakeCookie (req * http.Request , value string , expiration time.Duration , now time.Time ) * http.Cookie {
250
+ func (p * OAuthProxy ) MakeSessionCookie (req * http.Request , value string , expiration time.Duration , now time.Time ) * http.Cookie {
251
+ if value != "" {
252
+ value = cookie .SignedValue (p .CookieSeed , p .SessionCookieName , value , now )
253
+ }
254
+ return p .makeCookie (req , p .SessionCookieName , value , expiration , now )
255
+ }
256
+
257
+ func (p * OAuthProxy ) MakeCSRFCookie (req * http.Request , value string , expiration time.Duration , now time.Time ) * http.Cookie {
258
+ return p .makeCookie (req , p .CSRFCookieName , value , expiration , now )
259
+ }
260
+
261
+ func (p * OAuthProxy ) makeCookie (req * http.Request , name string , value string , expiration time.Duration , now time.Time ) * http.Cookie {
247
262
domain := req .Host
248
263
if h , _ , err := net .SplitHostPort (domain ); err == nil {
249
264
domain = h
@@ -255,11 +270,8 @@ func (p *OAuthProxy) MakeCookie(req *http.Request, value string, expiration time
255
270
domain = p .CookieDomain
256
271
}
257
272
258
- if value != "" {
259
- value = cookie .SignedValue (p .CookieSeed , p .CookieName , value , now )
260
- }
261
273
return & http.Cookie {
262
- Name : p . CookieName ,
274
+ Name : name ,
263
275
Value : value ,
264
276
Path : "/" ,
265
277
Domain : domain ,
@@ -269,20 +281,28 @@ func (p *OAuthProxy) MakeCookie(req *http.Request, value string, expiration time
269
281
}
270
282
}
271
283
272
- func (p * OAuthProxy ) ClearCookie (rw http.ResponseWriter , req * http.Request ) {
273
- http .SetCookie (rw , p .MakeCookie (req , "" , time .Hour * - 1 , time .Now ()))
284
+ func (p * OAuthProxy ) ClearCSRFCookie (rw http.ResponseWriter , req * http.Request ) {
285
+ http .SetCookie (rw , p .MakeCSRFCookie (req , "" , time .Hour * - 1 , time .Now ()))
274
286
}
275
287
276
- func (p * OAuthProxy ) SetCookie (rw http.ResponseWriter , req * http.Request , val string ) {
277
- http .SetCookie (rw , p .MakeCookie (req , val , p .CookieExpire , time .Now ()))
288
+ func (p * OAuthProxy ) SetCSRFCookie (rw http.ResponseWriter , req * http.Request , val string ) {
289
+ http .SetCookie (rw , p .MakeCSRFCookie (req , val , p .CookieExpire , time .Now ()))
290
+ }
291
+
292
+ func (p * OAuthProxy ) ClearSessionCookie (rw http.ResponseWriter , req * http.Request ) {
293
+ http .SetCookie (rw , p .MakeSessionCookie (req , "" , time .Hour * - 1 , time .Now ()))
294
+ }
295
+
296
+ func (p * OAuthProxy ) SetSessionCookie (rw http.ResponseWriter , req * http.Request , val string ) {
297
+ http .SetCookie (rw , p .MakeSessionCookie (req , val , p .CookieExpire , time .Now ()))
278
298
}
279
299
280
300
func (p * OAuthProxy ) LoadCookiedSession (req * http.Request ) (* providers.SessionState , time.Duration , error ) {
281
301
var age time.Duration
282
- c , err := req .Cookie (p .CookieName )
302
+ c , err := req .Cookie (p .SessionCookieName )
283
303
if err != nil {
284
304
// always http.ErrNoCookie
285
- return nil , age , fmt .Errorf ("Cookie %q not present" , p .CookieName )
305
+ return nil , age , fmt .Errorf ("Cookie %q not present" , p .SessionCookieName )
286
306
}
287
307
val , timestamp , ok := cookie .Validate (c , p .CookieSeed , p .CookieExpire )
288
308
if ! ok {
@@ -303,7 +323,7 @@ func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *p
303
323
if err != nil {
304
324
return err
305
325
}
306
- p .SetCookie (rw , req , value )
326
+ p .SetSessionCookie (rw , req , value )
307
327
return nil
308
328
}
309
329
@@ -333,7 +353,7 @@ func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, m
333
353
}
334
354
335
355
func (p * OAuthProxy ) SignInPage (rw http.ResponseWriter , req * http.Request , code int ) {
336
- p .ClearCookie (rw , req )
356
+ p .ClearSessionCookie (rw , req )
337
357
rw .WriteHeader (code )
338
358
339
359
redirect_url := req .URL .RequestURI ()
@@ -378,20 +398,18 @@ func (p *OAuthProxy) ManualSignIn(rw http.ResponseWriter, req *http.Request) (st
378
398
return "" , false
379
399
}
380
400
381
- func (p * OAuthProxy ) GetRedirect (req * http.Request ) (string , error ) {
382
- err := req .ParseForm ()
383
-
401
+ func (p * OAuthProxy ) GetRedirect (req * http.Request ) (redirect string , err error ) {
402
+ err = req .ParseForm ()
384
403
if err != nil {
385
- return "" , err
404
+ return
386
405
}
387
406
388
- redirect := req .FormValue ("rd" )
389
-
390
- if redirect == "" {
407
+ redirect = req .Form .Get ("rd" )
408
+ if redirect == "" || ! strings .HasPrefix (redirect , "/" ) || strings .HasPrefix (redirect , "//" ) {
391
409
redirect = "/"
392
410
}
393
411
394
- return redirect , err
412
+ return
395
413
}
396
414
397
415
func (p * OAuthProxy ) IsWhitelistedPath (path string ) (ok bool ) {
@@ -453,18 +471,24 @@ func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
453
471
}
454
472
455
473
func (p * OAuthProxy ) SignOut (rw http.ResponseWriter , req * http.Request ) {
456
- p .ClearCookie (rw , req )
474
+ p .ClearSessionCookie (rw , req )
457
475
http .Redirect (rw , req , "/" , 302 )
458
476
}
459
477
460
478
func (p * OAuthProxy ) OAuthStart (rw http.ResponseWriter , req * http.Request ) {
479
+ nonce , err := cookie .Nonce ()
480
+ if err != nil {
481
+ p .ErrorPage (rw , 500 , "Internal Error" , err .Error ())
482
+ return
483
+ }
484
+ p .SetCSRFCookie (rw , req , nonce )
461
485
redirect , err := p .GetRedirect (req )
462
486
if err != nil {
463
487
p .ErrorPage (rw , 500 , "Internal Error" , err .Error ())
464
488
return
465
489
}
466
490
redirectURI := p .GetRedirectURI (req .Host )
467
- http .Redirect (rw , req , p .provider .GetLoginURL (redirectURI , redirect ), 302 )
491
+ http .Redirect (rw , req , p .provider .GetLoginURL (redirectURI , fmt . Sprintf ( "%v:%v" , nonce , redirect ) ), 302 )
468
492
}
469
493
470
494
func (p * OAuthProxy ) OAuthCallback (rw http.ResponseWriter , req * http.Request ) {
@@ -489,8 +513,26 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
489
513
return
490
514
}
491
515
492
- redirect := req .Form .Get ("state" )
493
- if ! strings .HasPrefix (redirect , "/" ) || strings .HasPrefix (redirect , "//" ) {
516
+ s := strings .SplitN (req .Form .Get ("state" ), ":" , 2 )
517
+ if len (s ) != 2 {
518
+ p .ErrorPage (rw , 500 , "Internal Error" , "Invalid State" )
519
+ return
520
+ }
521
+ nonce := s [0 ]
522
+ redirect := s [1 ]
523
+ c , err := req .Cookie (p .CSRFCookieName )
524
+ if err != nil {
525
+ p .ErrorPage (rw , 403 , "Permission Denied" , err .Error ())
526
+ return
527
+ }
528
+ p .ClearCSRFCookie (rw , req )
529
+ if c .Value != nonce {
530
+ log .Printf ("%s csrf token mismatch, potential attack" , remoteAddr )
531
+ p .ErrorPage (rw , 403 , "Permission Denied" , "csrf failed" )
532
+ return
533
+ }
534
+
535
+ if ! strings .HasPrefix (redirect , "/" ) || strings .HasPrefix (redirect , "//" ) {
494
536
redirect = "/"
495
537
}
496
538
@@ -589,7 +631,7 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
589
631
}
590
632
591
633
if clearSession {
592
- p .ClearCookie (rw , req )
634
+ p .ClearSessionCookie (rw , req )
593
635
}
594
636
595
637
if session == nil {
0 commit comments