|
4 | 4 | package csrf
|
5 | 5 |
|
6 | 6 | import (
|
7 |
| - "crypto/subtle" |
8 |
| - "github.com/golang/glog" |
9 |
| - "github.com/robfig/revel" |
10 |
| - "net/url" |
11 |
| - "regexp" |
| 7 | + "crypto/subtle" |
| 8 | + "github.com/golang/glog" |
| 9 | + "github.com/robfig/revel" |
| 10 | + "net/url" |
| 11 | + "regexp" |
12 | 12 | )
|
13 | 13 |
|
14 | 14 | const (
|
15 |
| - cookieName = "csrf_token" |
16 |
| - fieldName = "csrf_token" |
17 |
| - headerName = "X-CSRF-Token" |
| 15 | + cookieName = "csrf_token" |
| 16 | + fieldName = "csrf_token" |
| 17 | + headerName = "X-CSRF-Token" |
18 | 18 | )
|
19 | 19 |
|
20 | 20 | var (
|
21 |
| - errNoReferer = "A secure request contained no Referer or its value was malformed." |
22 |
| - errBadReferer = "Same-origin policy failure." |
23 |
| - errBadToken = "CSRF tokens mismatch." |
| 21 | + errNoReferer = "REVEL_CSRF: A secure request contained no Referer or its value was malformed." |
| 22 | + errBadReferer = "REVEL_CSRF: Same-origin policy failure." |
| 23 | + errBadToken = "REVEL_CSRF: tokens mismatch." |
| 24 | + safeMethods = regexp.MustCompile("^(GET|HEAD|OPTIONS|TRACE)$") |
24 | 25 | )
|
25 | 26 |
|
26 | 27 | var CSRFFilter = func(c *revel.Controller, fc []revel.Filter) {
|
27 |
| - r := c.Request.Request |
| 28 | + r := c.Request.Request |
28 | 29 |
|
29 |
| - // OWASP; General Recommendation: Synchronizer Token Pattern. |
30 |
| - // CSRF tokens must be associated with the user's current session. |
31 |
| - tokenCookie, found := c.Session[cookieName] |
32 |
| - realToken := "" |
33 |
| - if !found { |
34 |
| - realToken = generateNewToken(c) |
35 |
| - } else { |
36 |
| - realToken = tokenCookie |
37 |
| - glog.V(0).Infof("Session's CSRF token: '%s'", realToken) |
38 |
| - if len(realToken) != tokenLength { |
39 |
| - // Wrong length; token has either been tampered with, we're migrating |
40 |
| - // onto a new algorithm for generating tokens, or a new session has |
41 |
| - // been initiated. In any case, a new token is generated and the |
42 |
| - // error will be detected later. |
43 |
| - glog.V(0).Infof("Bad CSRF token length: found %d, expected %d", |
44 |
| - len(realToken), tokenLength) |
45 |
| - realToken = generateNewToken(c) |
46 |
| - } |
47 |
| - } |
| 30 | + // [OWASP]; General Recommendation: Synchronizer Token Pattern: |
| 31 | + // CSRF tokens must be associated with the user's current session. |
| 32 | + tokenCookie, found := c.Session[cookieName] |
| 33 | + realToken := "" |
| 34 | + if !found { |
| 35 | + realToken = generateNewToken(c) |
| 36 | + } else { |
| 37 | + realToken = tokenCookie |
| 38 | + glog.V(2).Infof("REVEL-CSRF: Session's token: '%s'", realToken) |
| 39 | + if len(realToken) != lengthCSRFToken { |
| 40 | + // Wrong length; token has either been tampered with, we're migrating |
| 41 | + // onto a new algorithm for generating tokens, or a new session has |
| 42 | + // been initiated. In any case, a new token is generated and the |
| 43 | + // error will be detected later. |
| 44 | + glog.V(2).Infof("REVEL_CSRF: Bad token length: found %d, expected %d", |
| 45 | + len(realToken), lengthCSRFToken) |
| 46 | + realToken = generateNewToken(c) |
| 47 | + } |
| 48 | + } |
48 | 49 |
|
49 |
| - c.RenderArgs[fieldName] = realToken |
| 50 | + c.RenderArgs[fieldName] = realToken |
50 | 51 |
|
51 |
| - // See http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods |
52 |
| - safeMethod, _ := regexp.MatchString("^(GET|HEAD|OPTIONS|TRACE)$", r.Method) |
53 |
| - if !safeMethod { |
54 |
| - glog.V(0).Infof("Unsafe %s method...", r.Method) |
55 |
| - if r.URL.Scheme == "https" { |
56 |
| - // See OWASP; Checking the Referer Header. |
57 |
| - referer, err := url.Parse(r.Header.Get("Referer")) |
58 |
| - if err != nil || referer.String() == "" { |
59 |
| - // Parse error or empty referer. |
60 |
| - c.Result = c.Forbidden(errNoReferer) |
61 |
| - return |
62 |
| - } |
63 |
| - // See OWASP; Checking the Origin Header. |
64 |
| - if !sameOrigin(referer, r.URL) { |
65 |
| - c.Result = c.Forbidden(errBadReferer) |
66 |
| - return |
67 |
| - } |
68 |
| - } |
| 52 | + // See http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods |
| 53 | + unsafeMethod := !safeMethods.MatchString(r.Method) |
| 54 | + if unsafeMethod && !IsExempted(r.URL.Path) { |
| 55 | + glog.V(2).Infof("REVEL-CSRF: Processing unsafe '%s' method...", r.Method) |
| 56 | + if r.URL.Scheme == "https" { |
| 57 | + // See [OWASP]; Checking the Referer Header. |
| 58 | + referer, err := url.Parse(r.Header.Get("Referer")) |
| 59 | + if err != nil || referer.String() == "" { |
| 60 | + // Parse error or empty referer. |
| 61 | + c.Result = c.Forbidden(errNoReferer) |
| 62 | + return |
| 63 | + } |
| 64 | + // See [OWASP]; Checking the Origin Header. |
| 65 | + if !sameOrigin(referer, r.URL) { |
| 66 | + c.Result = c.Forbidden(errBadReferer) |
| 67 | + return |
| 68 | + } |
| 69 | + } |
69 | 70 |
|
70 |
| - // Accept CSRF token in the custom HTTP header X-CSRF-Token, as well as |
71 |
| - // in the form submission itself, for ease of use with popular JavaScript |
72 |
| - // toolkits which allow insertion of custom headers into all AJAX |
73 |
| - // requests. See http://erlend.oftedal.no/blog/?blogid=118 |
74 |
| - sentToken := r.Header.Get(headerName) |
75 |
| - if sentToken == "" { |
76 |
| - sentToken = c.Params.Get(fieldName) |
77 |
| - } |
78 |
| - glog.V(0).Infof("CSRF token received: '%s'", sentToken) |
| 71 | + sentToken := "" |
| 72 | + if ajaxSupport := revel.Config.BoolDefault("csrf.ajax", false); ajaxSupport { |
| 73 | + // Accept CSRF token in the custom HTTP header X-CSRF-Token, for ease |
| 74 | + // of use with popular JavaScript toolkits which allow insertion of |
| 75 | + // custom headers into all AJAX requests. |
| 76 | + // See http://erlend.oftedal.no/blog/?blogid=118 |
| 77 | + sentToken = r.Header.Get(headerName) |
| 78 | + } |
| 79 | + if sentToken == "" { |
| 80 | + // Get CSRF token from form. |
| 81 | + sentToken = c.Params.Get(fieldName) |
| 82 | + } |
| 83 | + glog.V(2).Infof("REVEL-CSRF: Token received from client: '%s'", sentToken) |
79 | 84 |
|
80 |
| - if len(sentToken) != len(realToken) { |
81 |
| - c.Result = c.Forbidden(errBadToken) |
82 |
| - return |
83 |
| - } else { |
84 |
| - comparison := subtle.ConstantTimeCompare([]byte(sentToken), []byte(realToken)) |
85 |
| - if comparison != 1 { |
86 |
| - c.Result = c.Forbidden(errBadToken) |
87 |
| - return |
88 |
| - } |
89 |
| - } |
90 |
| - } |
91 |
| - glog.V(0).Infoln("CSRF token successfully checked.") |
| 85 | + if len(sentToken) != len(realToken) { |
| 86 | + c.Result = c.Forbidden(errBadToken) |
| 87 | + return |
| 88 | + } |
| 89 | + comparison := subtle.ConstantTimeCompare([]byte(sentToken), []byte(realToken)) |
| 90 | + if comparison != 1 { |
| 91 | + c.Result = c.Forbidden(errBadToken) |
| 92 | + return |
| 93 | + } |
| 94 | + glog.V(2).Infoln("REVEL-CSRF: Token successfully checked.") |
| 95 | + } |
92 | 96 |
|
93 |
| - fc[0](c, fc[1:]) |
| 97 | + fc[0](c, fc[1:]) |
94 | 98 | }
|
95 | 99 |
|
96 | 100 | // See http://en.wikipedia.org/wiki/Same-origin_policy
|
97 | 101 | func sameOrigin(u1, u2 *url.URL) bool {
|
98 |
| - return (u1.Scheme == u2.Scheme && u1.Host == u2.Host) |
| 102 | + return (u1.Scheme == u2.Scheme && u1.Host == u2.Host) |
99 | 103 | }
|
100 | 104 |
|
101 |
| -// Generate a new CSRF token. |
102 |
| -func generateNewToken(c *revel.Controller) string { |
103 |
| - token := generateToken() |
104 |
| - glog.V(0).Infof("Generated new CSRF Token: '%s'", token) |
105 |
| - c.Session[cookieName] = token |
106 |
| - return token |
107 |
| -} |
0 commit comments