@@ -37,6 +37,7 @@ var SignatureHeaders []string = []string{
3737type OAuthProxy struct {
3838 CookieSeed string
3939 CookieName string
40+ CSRFCookieName string
4041 CookieDomain string
4142 CookieSecure bool
4243 CookieHttpOnly bool
@@ -174,6 +175,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
174175
175176 return &OAuthProxy{
176177 CookieName: opts.CookieName,
178+ CSRFCookieName: fmt.Sprintf("%v_%v", opts.CookieName, "csrf"),
177179 CookieSeed: opts.CookieSecret,
178180 CookieDomain: opts.CookieDomain,
179181 CookieSecure: opts.CookieSecure,
@@ -245,7 +247,22 @@ func (p *OAuthProxy) redeemCode(host, code string) (s *providers.SessionState, e
245247 return
246248}
247249
248−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.CookieName, value, now)
253+ if len(value) > 4096 {
254+ // Cookies cannot be larger than 4kb
255+ log.Printf("WARNING - Cookie Size: %d bytes", len(value))
256+ }
257+ }
258+ return p.makeCookie(req, p.CookieName, value, expiration, now)
259+}
260+
261+func (p *OAuthProxy) MakeCSRFCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
262+ return p.makeCookie(req, p.CSRFCookieName, value, expiration, now)
263+}
264+
265+func (p *OAuthProxy) makeCookie(req *http.Request, name string, value string, expiration time.Duration, now time.Time) *http.Cookie {
249266 domain := req.Host
250267 if h, _, err := net.SplitHostPort(domain); err == nil {
251268 domain = h
@@ -257,15 +274,8 @@ func (p *OAuthProxy) MakeCookie(req *http.Request, value string, expiration time
257274 domain = p.CookieDomain
258275 }
259276
260− if value != "" {
261− value = cookie.SignedValue(p.CookieSeed, p.CookieName, value, now)
262− if len(value) > 4096 {
263− // Cookies cannot be larger than 4kb
264− log.Printf("WARNING - Cookie Size: %d bytes", len(value))
265− }
266− }
267277 return &http.Cookie{
268− Name: p.CookieName,
278+ Name: name,
269279 Value: value,
270280 Path: "/",
271281 Domain: domain,
@@ -275,12 +285,20 @@ func (p *OAuthProxy) MakeCookie(req *http.Request, value string, expiration time
275285 }
276286}
277287
278−func (p *OAuthProxy) ClearCookie(rw http.ResponseWriter, req *http.Request) {
279− http.SetCookie(rw, p.MakeCookie(req, "", time.Hour*-1, time.Now()))
288+func (p *OAuthProxy) ClearCSRFCookie(rw http.ResponseWriter, req *http.Request) {
289+ http.SetCookie(rw, p.MakeCSRFCookie(req, "", time.Hour*-1, time.Now()))
290+}
291+
292+func (p *OAuthProxy) SetCSRFCookie(rw http.ResponseWriter, req *http.Request, val string) {
293+ http.SetCookie(rw, p.MakeCSRFCookie(req, val, p.CookieExpire, time.Now()))
280294}
281295
282−func (p *OAuthProxy) SetCookie(rw http.ResponseWriter, req *http.Request, val string) {
283− http.SetCookie(rw, p.MakeCookie(req, val, p.CookieExpire, time.Now()))
296+func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Request) {
297+ http.SetCookie(rw, p.MakeSessionCookie(req, "", time.Hour*-1, time.Now()))
298+}
299+
300+func (p *OAuthProxy) SetSessionCookie(rw http.ResponseWriter, req *http.Request, val string) {
301+ http.SetCookie(rw, p.MakeSessionCookie(req, val, p.CookieExpire, time.Now()))
284302}
285303
286304func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*providers.SessionState, time.Duration, error) {
@@ -309,7 +327,7 @@ func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *p
309327 if err != nil {
310328 return err
311329 }
312− p.SetCookie(rw, req, value)
330+ p.SetSessionCookie(rw, req, value)
313331 return nil
314332}
315333
@@ -339,7 +357,7 @@ func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, m
339357}
340358
341359func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code int) {
342− p.ClearCookie(rw, req)
360+ p.ClearSessionCookie(rw, req)
343361 rw.WriteHeader(code)
344362
345363 redirect_url := req.URL.RequestURI()
@@ -384,20 +402,18 @@ func (p *OAuthProxy) ManualSignIn(rw http.ResponseWriter, req *http.Request) (st
384402 return "", false
385403}
386404
387−func (p *OAuthProxy) GetRedirect(req *http.Request) (string, error) {
388− err := req.ParseForm()
389−
405+func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error) {
406+ err = req.ParseForm()
390407 if err != nil {
391− return "", err
408+ return
392409 }
393410
394− redirect := req.FormValue("rd")
395−
396− if redirect == "" {
411+ redirect = req.Form.Get("rd")
412+ if redirect == "" || !strings.HasPrefix(redirect, "/") || strings.HasPrefix(redirect, "//") {
397413 redirect = "/"
398414 }
399415
400− return redirect, err
416+ return
401417}
402418
403419func (p *OAuthProxy) IsWhitelistedPath(path string) (ok bool) {
@@ -459,18 +475,24 @@ func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
459475}
460476
461477func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
462− p.ClearCookie(rw, req)
478+ p.ClearSessionCookie(rw, req)
463479 http.Redirect(rw, req, "/", 302)
464480}
465481
466482func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
483+ nonce, err := cookie.Nonce()
484+ if err != nil {
485+ p.ErrorPage(rw, 500, "Internal Error", err.Error())
486+ return
487+ }
488+ p.SetCSRFCookie(rw, req, nonce)
467489 redirect, err := p.GetRedirect(req)
468490 if err != nil {
469491 p.ErrorPage(rw, 500, "Internal Error", err.Error())
470492 return
471493 }
472494 redirectURI := p.GetRedirectURI(req.Host)
473− http.Redirect(rw, req, p.provider.GetLoginURL(redirectURI, redirect), 302)
495+ http.Redirect(rw, req, p.provider.GetLoginURL(redirectURI, fmt.Sprintf("%v:%v", nonce, redirect)), 302)
474496}
475497
476498func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
@@ -495,8 +517,26 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
495517 return
496518 }
497519
498− redirect := req.Form.Get("state")
499− if !strings.HasPrefix(redirect, "/") || strings.HasPrefix(redirect, "//") {
520+ s := strings.SplitN(req.Form.Get("state"), ":", 2)
521+ if len(s) != 2 {
522+ p.ErrorPage(rw, 500, "Internal Error", "Invalid State")
523+ return
524+ }
525+ nonce := s[0]
526+ redirect := s[1]
527+ c, err := req.Cookie(p.CSRFCookieName)
528+ if err != nil {
529+ p.ErrorPage(rw, 403, "Permission Denied", err.Error())
530+ return
531+ }
532+ p.ClearCSRFCookie(rw, req)
533+ if c.Value != nonce {
534+ log.Printf("%s csrf token mismatch, potential attack", remoteAddr)
535+ p.ErrorPage(rw, 403, "Permission Denied", "csrf failed")
536+ return
537+ }
538+
539+ if !strings.HasPrefix(redirect, "/") || strings.HasPrefix(redirect, "//") {
500540 redirect = "/"
501541 }
502542
@@ -595,7 +635,7 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
595635 }
596636
597637 if clearSession {
598− p.ClearCookie(rw, req)
638+ p.ClearSessionCookie(rw, req)
599639 }
600640
601641 if session == nil {