In web applications, if you are using cookies for storing authentication state, you should be worrying about Cross Site Request Forgery (CSRF). In short, when using cookies, other sites can create an HTML form that submits to your backend. When your browser submits that form, it will attach any cookies it has that are associated with the URL that the form is submitted to. Without any steps taken to mitigate this, a malicious third party could create a form on their site that performs an action on application, maybe as harmless as following them on a site, or as malicious as deleting your account or transferring funds. OWASP has more details on CSRF attacks as well.
Typical CSRF prevention
The way that most applications mitigate CSRF attacks is by utilizing a synchronizer CSRF token that is stored in a hidden HTML input in the form which matches a token in the current user’s session. In short, malicious forms will be lacking this token and will be rejected. More details on token based mitigation are covered in this OWASP page.
What about Single Page applications? Is a JSON only API vulnerable?
CSRF tokens work well for server side rendered applications (SSR), since the HTML generated on the server can securely include the token that is in the session cookie. But what about single page application like Ember? Can you craft an HTML form to submit a valid request to a JSON based API? The answer is yes it is possible.
Ok, so how can we secure this API backend without a CSRF token?
Enter Referer/Origin header verification
With the exception of GET
requests, when your browser submits a
request, it attaches both a Referer
(that typo is historical) and
Origin
header. The Origin
header is a bit newer, it was originally
introduced for Cross Origin Resource Sharing (CORS), but has been
repurposed for CSRF mitigation as well. We can use the Referer
or
Origin
header to verify that the request originated from the domain we
expect. In this case, I prefer the Origin
header. When a request
originates at an HTTPS page and is made against an HTTP page, the
Referer
header is dropped, while the Origin
header will continue to
be sent. Both OWASP and Mozilla talk
about the validity of using the Origin
header as a way to mitigate
CSRF attacks.
Places where CSRF prevention fails
No matter how good your CSRF prevention is, there are other vectors that
will open your application to malicious requests. If your session cookie
is not HTTPOnly
(details), and/or you accidentally allow malicious JavaScript to
execute on your site, you have a couple of problems. When not using
HTTPOnly
cookies, your cookies can be retrieved via JavaScript, and if
you have malicious JavaScript on your site, someone could siphon off all
your session cookies. At that point, this third party could forge any
header it needs to, and then include the session cookie to automate requests
against your server, so it could bypass the Origin
check. They could
also replace the cookie in their browser with another person’s cookie
that they stole, and use your site as that user, bypassing CSRF Tokens.
If your cookie is marked as HTTPOnly
, they can still use malicious
JavaScript via a Cross Site Scripting (XSS) attack, since any users’
browser that executes this malicious JavaScript will make the request
with either the necessary CSRF token or Origin
header.
Ok, so why all this information about CSRF mitigation?
At DockYard, we produce Ember applications for our clients. Since Ember
is a single page application, Origin
header checking is the best way
we can protect any backends we produce. To the end, I recently published
VerifyOrigin to
hex.pm. VerifyOrigin
provides a Plug
you can add to your applications pipeline to only
allow requests from Origin
s you expect:
plug VerifyOrigin, ["https://example.com"]
The plug expects a list of valid Origin
URLs. If the request does not
have an Origin
header that matches your list, then it returns a 400 Bad Request
response to the client. Simple CSRF mitigation for your
Phoenix app is at your fingertips!
A special thanks to Craig Ingram, who I consulted with multiple times when coming up with this plug!