Skip to content

Commit fb14a44

Browse files
committed
Create csrf.go
1 parent 4021e84 commit fb14a44

File tree

1 file changed

+106
-0
lines changed

1 file changed

+106
-0
lines changed

csrf.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Synchronizer Token Pattern implementation.
2+
//
3+
// See [OWASP] https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet
4+
package csrf
5+
6+
import (
7+
"crypto/subtle"
8+
"github.com/robfig/revel"
9+
"net/url"
10+
"regexp"
11+
)
12+
13+
const (
14+
cookieName = "csrf_token"
15+
fieldName = "csrf_token"
16+
headerName = "X-CSRF-Token"
17+
)
18+
19+
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."
23+
)
24+
25+
var CSRFFilter = func(c *revel.Controller, fc []revel.Filter) {
26+
r := c.Request.Request
27+
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+
}
47+
48+
c.RenderArgs[fieldName] = realToken
49+
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+
}
68+
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 = r.PostFormValue(fieldName)
76+
}
77+
revel.TRACE.Printf("CSRF token received: '%s'", sentToken)
78+
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.")
91+
92+
fc[0](c, fc[1:])
93+
}
94+
95+
// See http://en.wikipedia.org/wiki/Same-origin_policy
96+
func sameOrigin(u1, u2 *url.URL) bool {
97+
return (u1.Scheme == u2.Scheme && u1.Host == u2.Host)
98+
}
99+
100+
// Generate a new CSRF token.
101+
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
106+
}

0 commit comments

Comments
 (0)