Labs/Bespin/Security

From MozillaWiki
< Labs‎ | Bespin
Jump to navigation Jump to search

CSRF Protection

Requirements

  • Stateless server. The solution should not require extra server side state. This prevents us from using server side sessions, or using a global dictionary to lookup tokens
  • Static HTML. Several security solutions require the manipulation of HTML on the way to the client. Given our desire to support multiple back-ends, this is not acceptable.
  • Should prevent Basic CSRF - So cookies alone are not sufficient to re-authenticate a client
  • Should prevent Login CSRF - So the identification token must be provided to the client prior to the initial authentication request. Since the server can't store information on auth-state (see above), this implies that 2 different types tokens must be used, one for pre-auth and one for post-auth
  • Should prevent Session Fixation - Session fixation is a problem for any site that allows URL re-writing, or has an XSS flaw, we just need to check that these don't apply to us!

Design

How it worked previously:

  • All of the pages already call /register/userinfo/ first thing to ensure that the user is logged in.
  • logged in users presently have a signed cookie that identifies them.
  • /register/userinfo/ currently returns some information if the user is logged in. Otherwise, it returns a 401.
  • /register/login/username sets the authentication cookie.
  • /register/logout/ deletes the authentication cookie
  • all authenticated URLs verify the cookie and use that to get the username.

New additional behavior:

  • In server.js: when the first remote call to the server is made the client first generates an unpredictable token, it places this value into a cookie called "Domain-Token".
  • On every call it recalls the value (or reads the token from the cookie), and places it in a header called "Domain-Token".

For example:

var token = dojo.cookie("Domain-Token");
if (!token) {
    token = server._randomPassword();
    dojo.cookie("Domain-Token", token);
}
xhr.setRequestHeader("Domain-Token", token);

Our implementation of _randomPassword() is as follows:

chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
pass = "";
for (var x = 0; x < 16; x++) {
    var charIndex = Math.floor(Math.random() * chars.length);
    pass += chars.charAt(charIndex);
}
return pass;
  • In framework.py: On receipt of an XHR call we extract the Domain-Token header and check the value is the same as the "Domain-Token" cookie, throwing an exception if they do not match
query_token = request.cookies.get("Domain-Token")
reply_token = environ.get("HTTP_DOMAIN_TOKEN")

if query_token is None or reply_token != query_token:
    response.error("401 Not Authorized", "CSRF Token Error")
  • In addition we introduced a skip_token_check parameter to the expose annotation to allow us to serve static resources without CSRF checks and a BespinTestApp that extended webtest.TestApp to inject a Header/Cookie as we expect the client to do.
  • Finally we expose this token to the server to allow it's use in message routing.
self.session_token = environ.get("HTTP_DOMAIN_TOKEN")