Our comms team told us we need an image; our legal team told us it needed to be freely licensed. Credit: Carsten Schertzer (Creative Commons Attribution 2.0).

Dropbox employs traditional cross-site attack defenses, but we also employ same-site cookies as a defense in depth on newer browsers. In this post, we describe how we rolled out same-site cookie based defenses on Dropbox, and offer some guidelines on how you can do the same on your website. Background Recently, the IETF released a new RFC introducing same-site cookies. Unlike traditional cookies, browsers will not send same-site cookies on cross-site requests. At Dropbox, we recently rolled out same-site cookies to defend against CSRF attacks and cross-site information leakage. We concluded that same-site cookies are a convenient way to reduce a website’s attack surface. Cross-site requests Many attacks on the web involve cross-site requests, including the well-known cross-site request forgery (CSRF) attack. These attacks trick the victim’s browser into performing an unintended request to a trusted website. Because users trust Dropbox with their most sensitive data, it's critical that we make our defenses against these attacks as strong as possible. What does a CSRF attack look like? As an example, let’s pretend Dropbox was naively not protecting against CSRF attacks. The attack starts when a victim visits an attacker-controlled website, say www.evil.com . The evil website then returns a page with a malicious payload. The browser executes this malicious payload, which makes a request to https://dropbox.com/cmd/delete and attempts to remove user data.

A hypothetical CSRF attack on Dropbox.

A classic CSRF defense is to introduce a random value token — called the CSRF token — and store its value in a cookie, say csrf_cookie , on the first page load. The browser sends the CSRF token as a request parameter on every “unsafe” request (POSTs). The server then compares the value of csrf_cookie to the request parameter and throws a CSRF error if these values do not match. Even if a website has CSRF defenses, it could be vulnerable to cross-origin information leakage attacks like cross-site search attacks and JSON hijacking. For example, let’s assume www.dropbox.com has an AJAX route /get_files . A GET request to /get_files gets all of the logged in user’s filenames. and the size of the response can leak side channel information about how many files are there in a user’s Dropbox. We now describe how we designed and implemented defenses against cross-site attacks on Dropbox using same-site cookies. Design For reliability and security, our design for same-site cookie protections should have the following requirements: CSRF defense: the defenses provided should be equivalent to our existing CSRF defense: all POST requests (by default) must be same-site requests, as they are state-changing. GET requests do not require any CSRF protection, but could still be responsible for a different class of cross-site vulnerabilities (see the next requirement).

the defenses provided should be equivalent to our existing CSRF defense: all POST requests (by default) must be same-site requests, as they are state-changing. GET requests do not require any CSRF protection, but could still be responsible for a different class of cross-site vulnerabilities (see the next requirement). Cross-site information leakage defense: AJAX GET requests should be same-site, as they are a source of many cross-site information leakage vulnerabilities.

AJAX GET requests should be same-site, as they are a source of many cross-site information leakage vulnerabilities. Availability: Our design should allow for careful rollout and easy rollback. We would still keep our existing CSRF defenses, as same-site cookie protection is still a defense in depth. Further, while rolling out, we should not break existing behavior on valid cross-site GET requests (e.g. a Dropbox link shared via email).

Our design should allow for careful rollout and easy rollback. We would still keep our existing CSRF defenses, as same-site cookie protection is still a defense in depth. Further, while rolling out, we should not break existing behavior on valid cross-site GET requests (e.g. a Dropbox link shared via email). Flexibility: Our design should be extensible to allow for cross-site defenses on a variety of request types and routes, not just POST requests and AJAX GET requests. Cookies become same-site by setting the attribute SameSite to one of two values, strict or lax . When strict , browsers will not send the same-site cookie on any cross-site request. When it’s lax , the browser will only prevent sending the cookie on “unsafe” requests (POSTs) but will allow “safe” requests (GETs). Let’s say Dropbox stores two cookies: a session_cookie and a csrf_cookie (we’re simplifying a tad). Further, a POST request to https://dropbox.com/ajax_login on the Dropbox site takes as input the user’s credentials and logs the user in (or equivalently, writes session_cookie ). Dropbox also has many “shared links” on pages with the format https://dropbox.com/sh/…/filename . Users can share these links over email and restrict access to these links. While brainstorming on how to add same-site cookie protections to Dropbox, we came up with the following naïve designs but quickly figured out that they were flawed: Add the SameSite attribute in strict enforcement mode to session_cookie . This makes all requests after logging in same-site and defends against CSRF and cross-information leakage for all authenticated users. Unfortunately, this can be problematic in cases where users navigate to a Dropbox link shared via email. This would cause cross-site GET requests with an authenticated user to not behave as expected, as the browser will not send that cookie.

Candidate design #1: Dropbox sets the session cookie as SameSite with enforcement mode strict on login.

Candidate design #1 isn’t ideal as a benign cross-site GET requests treat the user as logged out even if they were logged in.

Use lax enforcement mode for session_cookie instead of strict . As stated earlier, this would only give us basic CSRF protection, and wouldn’t help us with cross-origin information leakage attacks like cross-site search attacks and JSON hijacking with AJAX GET requests. Further, it’s weaker than our earlier CSRF protection as it doesn’t work for unauthenticated users (e.g. it doesn’t provide a defense against login CSRF attacks).

Candidate design #2: Dropbox sets the session cookie as SameSite with enforcement mode lax on login. CSRF defenses will only work for authenticated requests, e.g. the first login request could have been a cross-site request.

Have the CSRF cookie csrf_cookie as a SameSite cookie in strict enforcement mode. This, however, makes it harder to incrementally roll out and deal with unexpected failures. Further, this approach doesn’t work because we cryptographically bind the CSRF token to session_cookie to avoid session fixation. When we see the presence of session_cookie , but the absence or invalidity of csrf_cookie , we suspect session fixation and log the user out. Therefore, simple cross-site GET requests will still fail.

Candidate design #3: Dropbox sets the CSRF cookie as SameSite with enforcement mode strict on login.

Candidate design #3 isn’t ideal because we always check whether the session cookie was cryptographically bound to the CSRF token, even on benign GET requests.

Instead, we opted to introduce a new cookie, __Host-samesite_cookie . This cookie is SameSite with enforcement mode as strict . We set this cookie on all browsers that support same-site cookies, and we validate this cookie on every relevant request on the same browsers. The value of this cookie is derived from the CSRF token. We validate the same-site cookie by checking for its presence as well as the correctness of its value. We check for the value to defend against session fixation in case a cookie with the same name got set by an attacker previously.

Final design: Introduce a new cookie with SameSite set to strict and value derived from the CSRF token.

__Host-samesite_cookie has an enforcement mode strict , which means it does not get sent on benign cross-site GET requests (e.g. visiting a Dropbox public link from an external page). This is fine, as we can control enforcement on server-side. If the request is a benign GET request but __Host-samesite_cookie is absent, we can still allow the request to pass through. However, if it’s a state-changing POST request and __Host-samesite_cookie is absent, we can treat this as a CSRF error.

We can control enforcement on the server side. Benign GET requests will be allowed, as we can ignore samesite protection on non-AJAX GET requests.

As an aside, we made __Host-samesite_cookie a __Host-prefix cookie. A __Host- prefix cookie is a cookie that must only be sent to the host that sent the cookie. JavaScript on a subdomain of www.dropbox.com cannot set this cookie. If both csrf_cookie and __Host-samesite_cookie are valid, we can be confident that no session fixation attacks have occurred. Rollout Dealing with cookie authentication is very risky. It could create many availability and security issues. In the worst cases, it could lock many users out (and force them to manually reset their cookies), log users into another user’s account, or completely disable our CSRF defenses! Therefore, we decided to roll out same-site cookie defenses in two stages: first in “warnings-only” mode, where we log all errors, and later in “enforcement” mode when we see no unexpected errors. Further, we would want to be flexible in terms of what kinds of requests we would like to enforce the same-site check for. Enabling the same-site check for POST requests only would be equivalent to our current CSRF check, but wouldn’t necessarily be a defense against cross-origin information leakage or be helpful with entry-point investigation. To recap, we added a new cookie __Host-samesite_cookie for browsers that support the cookie. The cookie is SameSite with enforcement mode strict . Its value is derived from csrf_cookie . For the following requests, we check for presence of __Host-samesite_cookie and validate: If the request method is “POST”, and the route is NOT whitelisted for skipping CSRF protection, report a CSRF error if __Host-samesite_cookie is invalid. (Note that we allow some logging routes to skip CSRF protections.) If the request is AJAX and the request method is “GET”, report a potential cross-origin information leakage error if __Host-samesite_cookie is invalid. If the request is non-AJAX and the request method is “GET”, log the route as a potential entry point if __Host-samesite_cookie is invalid.

Copy if not is_present_and_valid(cookies.get("__Host-samesite_cookie")): if request.method == "POST" and not skip_csrf_check(route): # Case 1 report("CSRF error", route) elif request.type == "AJAX" and request.method == "GET": # Case 2 report("Cross-origin error", route) elif request.type != "AJAX" and request.method == "GET": # Case 3 log("entry_points_log", route) else: pass