Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing CSRF protection in a Python REST API

Writing a REST API with Pyramid/Cornice using JWT for authentication, I'll have to implement some CSRF protection. Having thoroughly read up on the topic I understand the problem, but I'm pretty confused about the best way to implement it, it's a bit tricky considering all the possible attack vectors.

Since this API gives access to sensitive data and will be published as open source software, it requires a self-contained protection. It will be used in environments with untrusted subdomains and I can not rely on users to follow security guidelines.

For my stateless service I can either use "Double Submit Cookies" or the "Encrypted Token Pattern"-method.

Double Submit Cookies

To prevent "cookie tossing", the token in the Double Submit method needs to be verifiable. MiTM attacks are an additional threat, which I hope to mitigate sufficiently by forcing HTTPS-cookies only.

To get a verifiable token that can't be easily guessed and replicated by an attacker, I imagine a hashed token like this should work:

pbkdf2_sha256.encrypt($userid + $expire + $mycsrfsecret, salt=$salt)

"exp" is the expire-value from the JWT. The JWT will be issued together with the CSRF-cookie and "exp" can be read by the server, which adds some additional protection as it's variable and the attacker doesn't know it (Might be superfluous?).

On a request I can easily compare the two tokens I receive with each other and use pbkdf2_sha256.verify($tokenfromrequest, $userid + $exp + $mycsrfsecret) to compare it with the values from the JWT-token ('Verifiablity').

Would that approach follow recommended practices?

I've selected pbkdf2 over bcrypt since its verify-method is noticeably quicker.

Expiry would be set to 7 days, after that both the JWT and the CSRF-token would be renewed by a fresh login (They would also be renewed on an intermediate relogin).

Encrypted Token Pattern

The alternative is to send a string to the client, consisting of userid, expiry and nonce, encrypted with a server-side secret. On a request this string is sent along and the server can decrypt it and verify userid and expiry.

This seems the simpler approach, but I'm unsure how to implement it, I don't intend to roll my own crypto and I have not found good examples:

  • What cipher/library should I use in Python? How do I do Encrypt-then-MAC?
  • How would I persist the token until its natural expiration? I don't want the users to have to login freshly every time they restart their browsers. Local Storage is not a safe place - but there is no alternative.
like image 222
Christian Benke Avatar asked Jan 31 '16 15:01

Christian Benke


People also ask

Is CSRF protection needed for REST API?

Enabling cross-site request forgery (CSRF) protection is recommended when using REST APIs with cookies for authentication. If your REST API uses the WCToken or WCTrustedToken tokens for authentication, then additional CSRF protection is not required.

How do you implement CSRF protection in Python?

To enable CSRF protection globally for a Flask app, register the CSRFProtect extension. CSRF protection requires a secret key to securely sign the token. By default this will use the Flask app's SECRET_KEY . If you'd like to use a separate token you can set WTF_CSRF_SECRET_KEY .

How does REST API implement CSRF?

If our project requires CSRF protection, we can send the CSRF token with a cookie by using CookieCsrfTokenRepository in a custom WebSecurityConfigurerAdapter. After restarting the app, our requests receive HTTP errors, which means that CSRF protection is enabled.


1 Answers

Writing a REST API with Pyramid/Cornice using JWT for authentication

While I am not familiar with those frameworks, I suggest you ensure the JWT token is passed within a HTTP header (e.g. My-JWT-Token: ... ) which is NOT the cookie. Then you do not have to worry about the CSRF vector.

Cross Site Request Forgery is an issue due to the nature of the browser's tendency to always submit cookies, which often contain authentication information, to a particular domain. A browser will not automatically submit a custom header, ergo you do not have to worry.

Double Submit Cookies

Your method is overly complicated, you could simply use a GUID. Put that GUID in a cookie, and put it in any other part of the request. If they equal, CSRF check passed. You could also put the GUID into the JWT, then validate the GUID is also in a header/body/query parameter.

Encrypted Token Pattern

This is almost exactly what JWT is, just pass the token in the header as suggested 😄

To answer the questions:

  • I would suggest hmac as in import hmac. I would not bother encrypting but merely ensure there is no sensitive information in the token. Else PyCrypto may do you well.
  • This is why cookies exist, which does raise the CSRF issue again. If this is a hard requirement then I suggest the double submit cookie method.
like image 126
Vetsin Avatar answered Nov 14 '22 23:11

Vetsin