Skip to content

Instantly share code, notes, and snippets.

@jamesarosen
Last active December 5, 2017 17:46
Show Gist options
  • Select an option

  • Save jamesarosen/6a2251d934ea497bbdad to your computer and use it in GitHub Desktop.

Select an option

Save jamesarosen/6a2251d934ea497bbdad to your computer and use it in GitHub Desktop.
RFC: Carefully Poking Holes in CSRF Protection

Background

I have an domain, api.example.com, that handles all API requests for my service. There are a select set of domains, [ www.example.com, *.www.example.com ], from which I wish to allow browser access. In addition to this, authorized clients should be able to access the API with non-browser clients (e.g. cURL). I have enabled CORS protections as best I can to allow www.example.com and *.www.example.com to make cross-origin requests to api.example.com.

Problem Statement

I wish to enable as much Cross-Site-Forgery-Request protection as possible on api.example.com while still allowing access from protected domains.

Traditional Solution

The traditional solution to CSRF protection is the Double-Submit Cookie. This solution works when the host page and any AJAX requests are made against the same domain. The server issues a separate csrf-token cookie valid only for that domain that the client then extracts and includes as a header with every AJAX request.

Unfortunately, setting a cookie on api.example.com would not allow the browser access to it, so it would not be able to set the header.

Option 1: Shared Cookie

We set the cookie on example.com so the browser has access. This is a relatively simple solution. Unfortunately, it greatly expands the risk profile of the service since an XSS attack on any *.example.com page would break the security model.

Option 2: Origin Header

http://stackoverflow.com/questions/24680302/csrf-protection-with-cors-origin-header-vs-csrf-token suggests that, when combined with good CORS headers, simply checking the Origin header on requests to api.example.com should be sufficient. I'm not convinced that's true, but I'm also not convinced it's false. One question is when should I check the Origin header? Non-browser API clients wouldn't send it. Should I only validate it if it's present, under the assumption that all modern browsers will set it correctly?

Option 3: iframe transport

  1. We create an HTML page, api.example.com/share-crsf-token. The response to that page -- and only that page -- includes the CSRF token as a cookie (on api.example.com).
  2. We set Content-Security-Policy: frame-ancestors www.example.com *.www.example.com to prevent sites other than those on our intended list from embedding this page in an iframe. (We can't use the deprecated X-Frame-Options because Chrome doesn't support the Allow-From option.) Older IE versions use X-Content-Security-Policy instead, so we set that to the same.
  3. JavaScript on that page checks to make sure document.referrer matches /^https://(.*\.)?www\.example\.com$/. If not, it aborts.
  4. JavaScript in the page extracts the cookie and calls window.parent.postMessage('csrf-token:jfdai13m402vaje', document.referrer) to announce the token to the parent window.
  5. JavaScript on www.example.com and *.www.example.com listen for message events, ensure that the sender is https://api.example.com, and then save the token in localstorage.

Risks:

  • not all browsers obey Content-Security-Policy
  • we're relying on the single-origin policy as applied to cookies and localstorage, as well as the security model of postMessage; that's a bigger surface than just cookies

Option 4: Cross-Domain Redirect on Login

On a successful POST https://api.example.com, we generate a token, jfdai13m402vaje, and redirect to https://www.example.com/set-csrf-token/jfdai13m402vaje (or a subdomain thereof, based on Referer). The resource www.example.com/set-csrf-token/[token] simply returns a cookie with the provided token.

The problem here is that a malicious third party could easily commit a minor denial-of-service attack against our users by forcing them to go to https://www.example.com/set-csrf-token/some-wrong-token. It wouldn't compromise confidentiality or integrity (since the attacker can't generate a cryptographically secure token), but it would effectively log the user out.

Summary

CSRF and CORS don't play together very nicely. These are just the ideas I've come up with. I'm hoping someone else has some better ones... or a good reason to pick one of these.

@jamesarosen
Copy link
Copy Markdown
Author

Some more ideas I've heard:

OAuth2

Implement an OAuth2 service provider system on the back-end. Use a short refresh period to gain the security that the double-submit cookie provides.

I think that's probably the best long-term solution I've heard. It does require building out a new authentication service, which can be expensive and slow to develop and deploy.

Single Domain, Proxy

Give up on using api.example.com directly from the browser. Instead, send them to the page's domain and use a path prefix to disambiguate API requests -- GET /api/posts/437.

You'll need some sort of proxy service like Varnish to forward the API requests.

The problem with this is links in the API responses. If using JSON API, your JSON response might look like

{
  "links": {
    "self": "https://api.example.com/posts/437"
  },
  "data": [{
    "type": "posts",
    "id": "1",

The proxy will have to rewrite all those URLs -- something that's both error-prone and slow.

You could mitigate that by having the API server use the X-Forwarded-For URL as the base.

@trajano
Copy link
Copy Markdown

trajano commented Dec 5, 2017

I'm actually looking into XSRF protection for https://github.com/trajano/app-ms though I already did an OAuth2.0 implementation for mine already. I am just trying to poke holes in it and gathering some ideas of attacks that may occur.

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