Created
July 7, 2018 14:52
-
-
Save simonerni/3501b8de6320ac37398d08d9d2d08561 to your computer and use it in GitHub Desktop.
Protect any origin from CSRF by checking if the Origin/Referer header match the Host header for "unsafe" methods.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* GET and HEAD requests are by definition idempotent and should be handled by the origin as such. Thus, we can safely pass them on without further origin / referer checks. | |
*/ | |
const safeMethods = ['GET','HEAD']; | |
const allowedMethods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE']; | |
addEventListener('fetch', event => { | |
event.respondWith(verifyAndFetch(event.request)) | |
}) | |
async function verifyAndFetch(request) { | |
/** | |
* If the request method is not in our allowed methods, deny the request right away. | |
*/ | |
if (allowedMethods.indexOf(request.method) === -1) { | |
return new Response('Sorry, this method is not allowed.', | |
{ status: 405, statusText: 'Method not allowed' }); | |
} | |
/** | |
* If the request is a save method, we can allow it without further checks. | |
*/ | |
if (safeMethods.indexOf(request.method) !== -1) { | |
return fetch(request); | |
} | |
let host = request.headers.get("Host"); | |
if (host === null) { | |
return new Response("No host header set", {status: 400, statusText: "Bad Request"}); | |
} | |
let source = null; | |
/** | |
* We prefer using the origin header over the referer header. | |
*/ | |
if (request.headers.has("Origin")) { | |
source = request.headers.get("Origin"); | |
} else if (request.headers.has("Referer")) { | |
source = request.headers.get("Referer"); | |
} | |
/** | |
* Let's try to extract the hostname from what we've got. | |
* | |
* "null" Origin will fail here. | |
*/ | |
try { | |
source = new URL(source).hostname; | |
} | |
catch(err) { | |
source = null; | |
} | |
console.log("Source: " + source + " , Target: " + host); | |
if (source === null) { | |
return new Response("Sorry, your request has neither Referer nor Origin set and is using a non safe method. Thus, your request has to be blocked because we can't be sure it's not a CSRF attack going on.", | |
{ status: 403, statusText: 'CSRF Protection' }); | |
} | |
if (host !== source) { | |
return new Response("Sorry, your request is a CORS request and we don't allow this here.", | |
{ status: 403, statusText: 'CSRF Protection' }); | |
} | |
return fetch(request); | |
} |
It's a forbidden header name:
https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name
Of course you can set it by yourself as the attacker, but that's not the point of CSRF protection.
Sorry to ask such a dumb question but.
Let's say i deployed this on a worker, what would i need to add to the current html to get CSRF protection?
<form action="https://www.paypal.com/donate" method="post" target="_blank"><input type="hidden" name="hosted_button_id" value="86Y77773U6EY6" /><input type="image" src="https://i.imgur.com/9QIhXan.webp" border="0" name="submit" title="PayPal - The safer, easier way to pay online!" alt="Donate with PayPal button" /><img alt="" border="0" width="1" height="1" /></form>
You're probably not PayPal, so the form will never pass through your worker, thus there is nothing you and I can do to provide CSRF protection to that form.
If you have the time, read this which explains CSRF:
https://portswigger.net/web-security/csrf
Thanks for the quick answer! I kinda did a bit of reading and understand it’s some simple form of session keys, thanks for the article!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
What's stopping someone from setting the origin header?