forked from roblillack/mars
-
Notifications
You must be signed in to change notification settings - Fork 0
/
csrf.go
132 lines (117 loc) · 3.87 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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package mars
import (
"crypto/rand"
"encoding/base64"
"fmt"
"html/template"
"net/http"
"time"
)
const csrfCookieKey = "_csrf"
const csrfCookieName = "CSRF"
const csrfHeaderName = "X-CSRF-Token"
const csrfFieldName = "_csrf_token"
func isSafeMethod(c *Controller) bool {
// Methods deemed safe as as defined RFC 7231, section 4.2.1.
// TODO: We might think about adding the two other idempotent methods here, too.
for _, i := range []string{"GET", "HEAD", "OPTIONS", "TRACE"} {
if c.Request.Method == i {
return true
}
}
return false
}
func findCSRFToken(c *Controller) string {
if h := c.Request.Header.Get(csrfHeaderName); h != "" {
return h
}
if f := c.Params.Get(csrfFieldName); f != "" {
return f
}
return ""
}
func generateRandomToken() string {
buf := make([]byte, 16)
if _, err := rand.Read(buf); err != nil {
ERROR.Printf("Error generating random CSRF token: %s\n", err)
return ""
}
return base64.RawURLEncoding.EncodeToString(buf)
}
// CSRFFilter provides measures of protecting against attacks known as
// "Cross-site request forgery" multiple ways in which the frontend of
// the application can prove that a mutating request to the server was
// actually initiated by the said frontend and not an attacker, that
// lured the user into calling unwanted on your site.
//
// A random CSRF token is added to the signed session (as key `_csrf`)
// and an additional Cookie (which can be read using JavaScript) called
// `XXX_CSRF`. The token is also available to the template engine as
// `{{.csrfToken}}` or as ready-made, hidden form field using
// `{{.csrfField}}`.
//
// For each HTTP request not deemed safe according to RFC 7231,
// section 4.2.1, one of these methods MUST be used for the server to
// ascertain that the user actually aksed to call this action in the
// first place:
//
// a) The token is sent using a custom header `X-CSRF-Token` with the
// request. This is very useful for single page application and AJAX
// requests, as most frontend toolkits can be set up to include this
// header if needed. An example for jQuery (added to the footer of each
// page) could look like this:
//
// <script type="text/javascript">
// $(function() {
// $.ajaxSetup({
// crossDomain: false,
// beforeSend: function(xhr, settings) {
// // HTTP methods that do not require CSRF protection.
// if (!/^(GET|HEAD|OPTIONS|TRACE)$/.test(settings.type)) {
// xhr.setRequestHeader("X-CSRF-Token", {{.csrfToken}});
// }
// }
// });
// });
// </script>
//
// b) The token is sent as a form field value for forms using non-safe
// actions. Simply adding `{{.csrfField}}`` should be enough.
//
// To disable CSRF protection for individual actions or controllers
// (ie. API calls that authenticate using HTTP Basic Auth or AccessTokens,
// etc.), add an InterceptorMethod to your Controller that sets the
// Controller.DisableCSRF to `true` for said requests.
//
// See also:
// https://tools.ietf.org/html/rfc7231#section-4.2.1
func CSRFFilter(c *Controller, fc []Filter) {
if DisableCSRF {
fc[0](c, fc[1:])
return
}
csrfToken := c.Session[csrfCookieKey]
if len(csrfToken) != 22 {
csrfToken = generateRandomToken()
c.Session[csrfCookieKey] = csrfToken
}
c.SetCookie(&http.Cookie{
Name: fmt.Sprintf("%s_%s", CookiePrefix, csrfCookieName),
Value: csrfToken,
Domain: CookieDomain,
Path: "/",
HttpOnly: false,
Secure: CookieSecure,
Expires: time.Now().Add(12 * time.Hour).UTC(),
})
c.RenderArgs["csrfToken"] = csrfToken
c.RenderArgs["csrfField"] = template.HTML(`<input type='hidden' name='` + csrfFieldName + `' value='` + csrfToken + `'/>`)
if !isSafeMethod(c) && !c.SkipCSRF {
token := findCSRFToken(c)
if token == "" || token != csrfToken {
c.Result = c.Forbidden("No/wrong CSRF token given.")
return
}
}
fc[0](c, fc[1:])
}