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