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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
|
Titre: Cross-Site Request Forgery is dead!
Auteur: Scott
Date: Mon 20 Feb 2017 11:15:24 +0100
Lien: https://scotthelme.co.uk/csrf-is-dead/
After toiling with Cross-Site Request Forgery on the web for, well forever
really, we finally have a proper solution. No technical burden on the site
owner, no difficult implementation, it's trivially simple to deploy, it's
Same-Site Cookies.
As old as the Web itself
Cross-Site Request Forgery, also known as CSRF or XSRF, has been around
basically forever. It stems from the simple capability that a site has to issue
a request to another site. Let's say I embed the following form in this very
page.
<>form action="https://your-bank.com/transfer" method="POST" id="stealMoney">
<>input type="hidden" name="to" value="Scott Helme">
<>input type="hidden" name="account" value="14278935">
<>input type="hidden" name="amount" value="£1,000">
Your browser loads this page and as a result the above form which I then submit
using a simple piece of JS on my page too.
document.getElementById("stealMoney").submit();
This is where the name CSRF comes from. I'm Forging a Request that is being
sent Cross-Site to your bank. The real problem here is not that I sent the
request but that your browser will send your cookies with it. The request will
be sent with the full authority you currently hold at this time, which means if
you're logged in to your bank you just donated £1,000 to me. Thanks! If you
weren't logged in then the request would be harmless as you can't transfer
money without being logged in. There are currently a few ways that your bank
can mitigate these CSRF attacks.
CSRF mitigations
I won't detail these too much because there are heaps of info available about
this topic on the web but I want to quickly cover them to show the technical
requirements to implement them.
Check the origin
When receiving a request we potentially have two pieces of information
available to us that indicate where the request came from. These are the Origin
header and the Referer header. You can check one or both of these values to see
if the request originated from a different origin to your own. If the request
was cross-origin you simply throw it away. The Origin and Referer header do get
some protection from browsers to prevent tampering but they may not always be
present either.
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
accept-encoding: gzip, deflate, br
cache-control: max-age=0
content-length: 166
content-type: application/x-www-form-urlencoded
dnt: 1
origin: https://report-uri.io
referer: https://report-uri.io/login
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
Anti-CSRF tokens
There are two different ways you can use Anti-CSRF tokens but the principle
remains the same. When a visitor requests a page, like the transfer money page
in the example above, you embed a random token into the form. When the genuine
user submits this form the random token is returned and you can check it
matches the one you issued in the form. In the CSRF attack scenario the
attacker can never get this value and couldn't get it even if they requested
the page because the Same Origin Policy (SOP) would prevent the attacker from
reading the response that contains the token. This method works well but
requires the site to track the issuance and return of the Anti-CSRF tokens. A
similar method is embedding the token into the form and issuing the browser a
cookie that contains the same value. When the genuine user submits their form
the value in the cookie and the form will match when received by the site. When
the attacker sends the forged request the browser won't have the CSRF cookie
set and the test will fail.
<>form action="https://report-uri.io/login/auth" method="POST">
<>input type="hidden" name="csrf_token" value="d82c90fc4a14b01224gde6ddebc23bf0">
<>input type="email" id="email" name="email">
<>input type="password" id="password" name="password">
<>button type="submit" class="btn btn-primary">Login<>/button>
<>/form>
The Problem
The methods above have given us fairly robust protection against CSRF for a
long time. Checking the Origin and Referer header isn't 100% reliable and most
sites resort to some variation of the Anti-CSRF token approach. The trouble is
though that these both put some kind of requirement on the site to implement
and maintain the solution. They might not be the most technically complicated
things in the world but we're still building a solution to work around the
browser doing something that we just don't want it to do. Instead, why don't we
just tell the browser to stop doing the thing we don't want it to do?... Now we
can!
Same-Site Cookies
You may have seen Same-Site Cookies mentioned in my recent blog called Tough
Cookies[1] but I'm going to go a little deeper into it here with some examples
too. Essentially, Same-Site Cookies completely and effectively neutralise CSRF
attacks. Dead. Finito. Adios! Capturing the essence of what we really need on
the web to win the security battle, Same-Site Cookies are simple to deploy,
really simple. Take your existing cookie:
Set-Cookie: sess=abc123; path=/
Simply add the SameSite attribute.
Set-Cookie: sess=abc123; path=/; SameSite
You're done. Seriously, that's it! Enabling this attribute on the cookie will
instruct the browser to afford this cookie certain protections. There are two
modes that you can enable this protection in, Strict or Lax, depending on how
serious you want to get. Specifying the SameSite attribute in your cookie with
no setting will default to Strict mode but you can also set Strict or Lax
explicitly if you wish.
SameSite=Strict
SameSite=Lax
Strict
Setting your SameSite protections to Strict mode is obviously the preferred
choice but the reason we have two options is that not all sites are the same
nor do they have the same requirements. When operating in Strict mode the
browser will not send the cookie on any cross-origin request, at all, so CSRF
is completely dead in the water. The only problem you might come across is that
it also won't send the cookie on top-level navigations (changing the URL in the
address bar) either. If I presented a link to https://facebook.com[2] and
Facebook had SameSite cookies set to Strict mode, when you clicked that linked
to open Facebook you wouldn't be logged in. Whether you were logged in already
or not, opened it in a new tab, whatever you did, you wouldn't be logged in to
Facebook when visiting from that link. This could be a little annoying and/or
unexpected to users but does offer incredibly robust protection. What Facebook
would need to do here is similar to what Amazon do, they have 2 cookies. One is
kind of a 'basic' cookie that identifies you as a user and allows you to have
the logged-in experience but if you want to do something sensitive like make a
purchase or change something in your account you need the second cookie, the
'real' cookie that allows you to do important things. The first cookie in this
case wouldn't have the SameSite attribute set as it's a 'convenience' cookie,
it doesn't really allow you to do anything sensitive and if the attacker can
make cross-origin requests with that, nothing happens. The second cookie
however, the sensitive cookie, would have the SameSite attribute set and the
attacker can't abuse its authority in cross-origin requests. This is the ideal
solution both for the user and for security. This isn't always possible though
and because we want SameSite cookies to be easy to deploy, there's a second
option.
Lax
Setting the SameSite protections to Lax mode fixes the problem mentioned above
in Strict mode of a user clicking on a link and not being logged in on the
target site if they were already logged in. In Lax mode there is a single
exception to allow cookies to be attached to top-level navigations that use a
safe HTTP method. The "safe" HTTP methods are defined in Section 4.2.1 of RFC
7321[3] as GET, HEAD, OPTIONS and TRACE, with us being interested in the GET
method here. This means that our top-level navigation to https://facebook.com[2]
when the user clicks the link now has SameSite flagged cookies attached when
the browser makes the request, maintaining the expected user experience. We're
also still completely protected against POST based CSRF attacks. Going back to
the example right at the top, this attack still wouldn't work in Lax mode.
<>form action="https://your-bank.com/transfer" method="POST" id="stealMoney">
<>input type="hidden" name="to" value="Scott Helme">
<>input type="hidden" name="account" value="14278935">
<>input type="hidden" name="amount" value="£1,000">
Because the POST method isn't considered safe, the browser wouldn't attach the
cookie in the request. The attacker is of course free to change the method to a
'safe' method though and issue the same request.
<>form action="https://your-bank.com/transfer" method="GET" id="stealMoney">
<>input type="hidden" name="to" value="Scott Helme">
<>input type="hidden" name="account" value="14278935">
<>input type="hidden" name="amount" value="£1,000">
As long as we don't accept GET requests in place of POST requests then this
attack isn't possible, but it's something to note when operating in Lax mode.
Also, if an attacker can trigger a top-level navigation or pop a new window
they can also cause the browser to issue a GET request with the cookies
attached. This is the trade-off of operating in Lax mode, we keep the user
experience intact but there is a small amount of risk to accept as payment.
Additional uses
This blog is aimed at mitigating CSRF with SameSite Cookies but there are, as
you may have guessed, other uses for this mechanism too. The first of those
listed in the spec is Cross-Site Script Inclusion (XSSI), which is where the
browser makes a request for an asset like a script that will change depending
on whether or not the user is authenticated. In the Cross-Site request scenario
an attacker can't abuse the ambient authority of a SameSite Cookie to yield a
different response. There are also some interesting timing attacks detailed
here[4] that can be mitigated too.
Another interesting use that isn't detailed is protection against leaking the
value of the session cookie in BEAST-style attacks against compression (CRIME[5]
, BREACH[6], HEIST[7], TIME[8]). This is really high level but the basic
scenario is that a MiTM can force the browser to issue requests cross-origin
via any mechanism they like and monitor them. By abusing the change in the size
of request payloads the attacker can guess the session ID value one byte at a
time by altering the requests the browser makes and observing their size on the
wire. Using SameSite Cookies the browser would not include cookies in such
requests and as a result the attacker cannot guess their value.
Browser Support
With most new security features in browsers you can expect either Firefox or
Chrome to be leading the charge and things are no different here either. Chrome
has had support for Same-Site Cookies since v51 which means that Opera, Android
Browser and Chrome on Android also has support. You can see details on
caniuse.com[9] that lists current support and Firefox has a bug[10] open to add
support too. Even though support isn't widespread yet we should still add the
SameSite attribute to our cookies. Browsers that understand it will respect the
setting and afford the cookie extra protection while those that don't will
simply ignore it and carry on. There's nothing to lose here and it forms a very
nice defence-in-depth approach. It's going to be a long time until we can
consider removing traditional anti-CSRF mechanisms but adding SameSite on top
of those gives us an incredibly robust defence.
Liens:
[1]: https://scotthelme.co.uk/tough-cookies/ (lien)
[2]: https://facebook.com (lien)
[3]: https://tools.ietf.org/html/rfc7231#section-4.2.1 (lien)
[4]: https://www.contextis.com/documents/2/Browser_Timing_Attacks.pdf (lien)
[5]: https://en.wikipedia.org/wiki/CRIME_(security_exploit) (lien)
[6]: https://en.wikipedia.org/wiki/BREACH_(security_exploit) (lien)
[7]: https://tom.vg/papers/heist_blackhat2016.pdf (lien)
[8]: https://www.blackhat.com/eu-13/briefings.html#Beery (lien)
[9]: http://caniuse.com/#search=SameSite (lien)
[10]: https://bugzilla.mozilla.org/show_bug.cgi?id=795346 (lien)
|