Skip to content

Instantly share code, notes, and snippets.

@simonerni
Created July 7, 2018 14:52
Show Gist options
  • Save simonerni/3501b8de6320ac37398d08d9d2d08561 to your computer and use it in GitHub Desktop.
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.
/**
* 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);
}
@Phara0h
Copy link

Phara0h commented Jun 10, 2020

What's stopping someone from setting the origin header?

@simonerni
Copy link
Author

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.

@Macleykun
Copy link

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>

@simonerni
Copy link
Author

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

@Macleykun
Copy link

Macleykun commented Jan 22, 2021 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment