GoogleCloudPlatform / emblem

Archived: Emblem Giving is a sample application that demonstrates a serverless architecture with continuous delivery, and trouble recovery. :diamond_shape_with_a_dot_inside:
Apache License 2.0
239 stars 61 forks source link

Design: figure out a CSRF protection method #140

Open ace-n opened 3 years ago

ace-n commented 3 years ago

CSRF

Cross-site request forgery (CSRF or XSRF) is a security vulnerability whereby one website impersonates an end-user while making a request to another website.

For example, badsite.com might submit a request to cymbalgiving.com/donate pointing to a campaign run by the attacker.

Mitigation

Token generation

The standard way to mitigate CSRF attacks is to include a randomly-generated, secure token in the page, and then ensure that any submitted requests have the proper token value.

There are a few ways to create such a token: 1) Generate tokens arbitrarily, and record them in a database 2) Generate tokens systematically, and verify that the client sends the right token based on the other request elements.

Of these, Option 1 is marginally more secure if the tokens generated are truly random. (Note: most "random" numbers on a computer are in fact pseudo-random numbers based off of a[n ostensibly] random seed value, such as the current UNIX timestamp.)

The risk of Option 2 is that if an attacker somehow managed to understand the app's inner workings, they could (in theory) predict subsequent CSRF token values - and in doing so defeat the point of the token. This is pretty unlikely in the absence of other vulnerabilities, though - and it's also easier for us to implement since it requires no state management.

Recommendation: prioritize ease-of-use (at least for now) and go with Option 2.

Possible token generation systems

In order to generate a token securely, we need to combine several properties together into a single hash. (The more properties, the harder it is to guess the token beforehand!)

A per-session property (such as an ID token) combined with a per-server secret would likely give us a passable level of protection. For additional security, we could also add a per-page value (such as the current URL) and/or a pseudorandom value known as a nonce that would be stored in the page itself. Basically, the more randomness/entropy the token contains, the better.

The implementation itself might look like this:

csrf_token = bcrypt(firebase_id_token + server_secret [ + destination_url + nonce])

*Note: longer hashes are exponentially harder to break - think hash cracking difficulty ~ O(c^{input length}) - and much easier to generate (`generation time = O(c {input length})`).

The server secret can be rotated in the event of it being compromised. This would (only) require users with active sessions to log back in to the app, and would be otherwise unnoticeable to end-users.

Recommendation: use the following algorithm to generate CSRF tokens:

def compute_csrf_token(nonce):
    server_secret = "some long random string from website/config.py"

    // Omit destination URL unless it's easy to get via Flask

    csrf_token = bcrypt(firebase_id_token + server_secret + nonce)

def route_get():
    nonce = uuid4()
    token = compute_csrf_token(nonce)

    // Add nonce and token values to rendered HTML
    return render_template("blah.html", nonce=nonce, token=token)

def route_post():
    nonce = request.form["nonce"]

    actual_token = request.form["csrf_token"]
    expected_token = compute_csrf_token(nonce)

    if expected_token != actual_token:
        throw "Invalid CSRF token"
grayside commented 3 years ago

https://firebase.google.com/docs/auth/admin/manage-cookies#python has content aimed at CSRF protection. Is something not usable there for our use case?