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