Giter Club home page Giter Club logo

flask-seasurf's Introduction

Tests Version Supported Python Versions

Flask-SeaSurf

SeaSurf is a Flask extension for preventing cross-site request forgery (CSRF).

CSRF vulnerabilities have been found in large and popular sites such as YouTube. These attacks are problematic because the mechanism they use is relatively easy to exploit. This extension attempts to aid you in securing your application from such attacks.

This extension is based on the excellent Django middleware.

Installation

Install the extension with one of the following commands:

$ easy_install flask-seasurf

or alternatively if you have pip installed:

$ pip install flask-seasurf

Usage

Using SeaSurf is fairly straightforward. Begin by importing the extension and then passing your application object back to the extension, like this:

from flask_seasurf import SeaSurf
csrf = SeaSurf(app)

Documentation

The Sphinx-compiled documentation is available here: https://flask-seasurf.readthedocs.io/

flask-seasurf's People

Contributors

alanhamlett avatar alekzvik avatar bjudson avatar boatx avatar c4rlo avatar crazyguitar avatar cybertoast avatar dplepage avatar fsx avatar gaborfeher avatar jesseops avatar jgelens avatar jpvanhal avatar killthekitten avatar klinkin avatar lpmi-13 avatar macrotim avatar martynsmith avatar maxcountryman avatar mmautner avatar monostop avatar nappypirate avatar ninadmhatre avatar nk9 avatar rspitler avatar ryankshaw avatar simon04 avatar skylerwilliams avatar thomasst avatar zdexter avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flask-seasurf's Issues

Safari Caching Issues

No matter whether you're working with include or exclude settings, Seasurf always adds "Cookie" to response.vary (=Vary: Accept-Encoding, Cookie) which apparently makes Safari ignore any kind of Caching headers. I.e., the static folder in Flask will never be cached on the client.

New version for PyPi

Hello,

Are there plans to release a newer version to PyPi?

Current version on PyPi is v1.1.1 with warnings thrown when using seasurf with the latest version of Flask with regards to the deprecated flask._app_ctx_stack. I see that it has been fixed on GitHub, but in PyPi its still outdated. Thanks!

Anyone who needs this issue to be fixed while waiting you can do put the following into your requirements.txt file:
Flask-SeaSurf @ https://github.com/maxcountryman/flask-seasurf/archive/refs/heads/main.tar.gz#sha256=64048ed1f01d6bf6ae369112eb70a21dcbbb6612eddd3a9069d8f49353c35400

or perform:
pip install https://github.com/maxcountryman/flask-seasurf/archive/refs/heads/main.tar.gz

remove secret from hashed cookie value

I would like to discuss the reason behind using the app's secret key in the hashed csrf token cookie value.

Hashing the secret key in the CSRF token cookie value means if someone discovers how to predict python's random number generation then they will have an unsalted hash of the app's secret key.

The point of FlaskSeaSurf is to validate the X-CSRFToken header value equals the randomly generated CSRF token cookie value. This ensures the request is coming code running on the same domain as the csrf token cookie. How does using the app's secret key provide any added security?

Some info on when Python's random number generator becomes predictable:

http://www.godsmonsters.com/Features/how-random-python/

http://stackoverflow.com/questions/2145510/python-random-is-barely-random-at-all

https://spideroak.com/blog/20121205114003-exploit-information-leaks-in-random-numbers-from-python-ruby-and-php

_before_request JSON read issue

There is an issue with trying to read JSON data from a request in the _before_request method of Flask-Seasurf:

File "/opt/virtualenvs/backend/lib/python3.4/site-packages/flask_seasurf.py", line 270, in _before_request
    request_csrf_token = request.json.get(self._csrf_name, '')
AttributeError: 'list' object has no attribute 'get'

As JSON data may come in the form of either a "collection of name/value pairs" or "an ordered list of values" (http://www.json.org/), this .get is unsafe as seen in the above error.

Updated Pypi release

The last release to pypi was 0.2.2 on 3/16/2016. It looks like some of the commits since then are nice to have published. Is there another release planned?

Way to select session or cookie based tokens?

With the django middleware the default settings are such that the CSRF token is stored in a cookie, with an optional setting to store in a server side session instead of using the cookie.

With Flask-Seasurf it appears that the default is to store the token in both the session and the cookie, but always use the session to validate against. I can't see a way to configure things to set just the session and not the cookie, or to use just the cookie and not store/validate against the session.

There is a disable_cookie() decorator, but that appears to disable session based storage too (as the session is actually updated in _set_session_cookie), Not sure if that's intentional - feels like a bug?

Ideally it would be nice for Flask-Seasurf to have the same flexibility as the django middleware, but if not it would be good to get a way to optionally disable the cookie as it doesn't seem to be strictly required.

Token validation for GET requests

I've been reading about CSRF and it looks like one should only do retrieval with GET request and everything else with a POST request. This is quite inconvenient with a list of delete links in a list or a logout link.

CORS and CSRF

If I have specified 3rd party origins (cors) access to particular views, wouldn't the referrer checking (in https) always return a 403 because the origins would never match? In short, I would have to disable csrf protection for those views

Use token from Flask's session

Keep csrf_token cookie for AJAX requests, but use the token from Flask's signed session cookie when validating the token.

This prevents a situation where somehow the user's csrf_token cookie was set by an attacker using Javascript.

Add release notes/changelog

Hello,

Can you provide release notes/changelog for changes in each version instead of looking in each commits?

Thank you.

outdated docstring

"""
...
This will assign a token to both the session cookie and the rendered HTML
which will then be validated on the backend.
...
"""

This is outdated as you do not store the csrf token into the session cookie currently (but into a separate cookie).

disable_cookie prevents token value being stored in session

I might be missing something here, but there seems to be a flaw with the disable_cookie logic.

If I set disable_cookie to True using app.csrf.disable_cookie(lambda r: True) Seasurf stops returning the set-cookie header as expected. However, the token value it generated (in its _before_request function) only seems to get set into session during the _set_csrf_cookie function, which is now bypassed because I have disabled_cookies. Since the token is never set into session, a new token will be generated on every request so I will never be able to get CSRF to pass validation.

The only way around this is to call current_app.csrf.generate_new_token() in the view function, as this function does set the new token value into session, but that means a new token value will be generated every request (thankfully previous ones will validate before they change).

Would it not make more sense to set the token value into session in _before_request when it is generated? Perhaps I am missing something obvious here?

Document testing?

The fact that you can disable testing with app.config["TESTING"] = True is amazing.

I'm not sure about the security implementations but please document this in the official documentations.

tag 0.2.2 missing in git

there was a release on pypi, but no tag in git.

as a consequence, when looking at "github releases" page, the project appears more stale than it is.

wrong comparison, making it potentially pointless

i think the csrf token must not just be stored in a cookie.

A cookie comes from the client. The cookie name is known and the client (attacker) can (at least potentially) put any value it likes in there. Maybe not easily (different domain) , but browsers could have bugs also...

Then, you compare that value against another value (the csrf token form field or http header value) that also comes from same client.

So, if client just puts same value at both places, it can do any csrf it likes.

If the csrf token is not read from the cookie, but from the session (which either comes from a file on the server or from a signed SecureCookie), the client can not tamper it.

See also there: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29_Prevention_Cheat_Sheet

And there: https://docs.djangoproject.com/en/dev/ref/contrib/csrf/
"""
Limitations

Subdomains within a site will be able to set cookies on the client for the whole domain. By setting the cookie and using a corresponding token, subdomains will be able to circumvent the CSRF protection. The only way to avoid this is to ensure that subdomains are controlled by trusted users (or, are at least unable to set cookies). Note that even without CSRF, there are other vulnerabilities, such as session fixation, that make giving subdomains to untrusted parties a bad idea, and these vulnerabilities cannot easily be fixed with current browsers.
"""

New path

When installing the module from the file (easy_install and pip do not function since rep is empty), the path of flask Seasurf is now :
from flask_seasurf import SeaSurf

Document options loaded from app.config in init_app

The init_app function loads some options from app.config, I think this should be added to the docs, as it provides a mean of configuration.

self._csrf_name = app.config.get('CSRF_COOKIE_NAME', '_csrf_token')
self._csrf_header_name = app.config.get('CSRF_HEADER_NAME',
                                                'X-CSRFToken')
self._csrf_disable = app.config.get('CSRF_DISABLE',
                                            app.config.get('TESTING', False))
self._csrf_timeout = app.config.get('CSRF_COOKIE_TIMEOUT',
                                            timedelta(days=5))
self._csrf_secure = app.config.get('CSRF_COOKIE_SECURE', False)
self._csrf_httponly = app.config.get('CSRF_COOKIE_HTTPONLY', False)
self._type = app.config.get('SEASURF_INCLUDE_OR_EXEMPT_VIEWS',
                                    'exempt')

Especially CSRF_COOKIE_NAME which can be set to 'csrf_token' to get compatibility with Flask-Admin.

Python Code Injection

While running a dynamic scan against an app protected by seasurf, Burp Suite Pro flagged an issue with the CSRF token.

For a GET operation, it altered the _csrf_token value as below, and raised an alarm when the app didn't respond for > 20 seconds, indicating the sleep operation was successfully run, indicating a Python injection vulnerability.

Cookie: _csrf_token=eval(compile('for%20x%20in%20range(1)%3a%5cn%20import%20time%5cn%20time.sleep(20)'%2c'a'%2c'single'));

httponly cookie?

after dealing with issue #20 (and IF you still have the code for the separate cookie then), consider using a httponly=True cookie (or add a comment about why it should be False to that place in the source).

Can't add exempt for Blueprints

Anyone got a hint how to add a Blueprint's route to the _exempt_views set?
Something like csrf._exempt_views.add("bp_user.someroute")
Importing the root @csrf.exempt decorator in the Blueprint doesn't seem to work either

Ability to provide custom error pages for CSRF errors

It would be nice to provide custom error page content when CSRF tokens fail to validate.

For example, when someone has disabled sending the Referer header in their browser they are presented with a 403 page on any POST request. We should be able to show a custom 403 page that says their request failed because they are missing the Referer header, so they can change their browser settings instead of just thinking the website is broken.

I'm thinking inside the Flask custom 403 error handler we could call csrf.get_error() which would return one of the error message constants (REASON_NO_REFERER, REASON_BAD_REFERER, ...) or None if the 403 was not triggered by Flask-SeaSurf.

This way a user could say:

@app.errorhandler(403)
def forbidden(e):
    csrf_error = csrf.get_error()
    if csrf_error:
        return render_template('custom_csrf_403.html', error=csrf_error), 403
    else:
        return render_template('generic_app_403.html'), 403

An alternative implementation is for Flask-SeaSurf to raise the HTTPException instead of calling abort(403) which adds the ability to change the exception's description attribute. The app's custom error handler is passed that exception, and could customize the error page with the improved description.

I'm happy to submit a pull request once/if we decide between these two implementations โšก

Tests failing

Tests are currently failing on master for Python 3.3 and 3.5 (missing tokens in 4 tests). I'm planning to look into this during a pairing session next week, and thought I'd create an issue in case anyone has more context to share before we start digging in.

unittest.makeSuite() is deprecated in Python 3.11 and will be removed in Python 3.13

Ref : python/cpython#28382

suite.addTest(unittest.makeSuite(SeaSurfTestCase))
suite.addTest(unittest.makeSuite(SeaSurfTestCaseExemptViews))
suite.addTest(unittest.makeSuite(SeaSurfTestCaseIncludeViews))
suite.addTest(unittest.makeSuite(SeaSurfTestCaseExemptUrls))
suite.addTest(unittest.makeSuite(SeaSurfTestCaseDisableCookie))
suite.addTest(unittest.makeSuite(SeaSurfTestCaseSkipValidation))
suite.addTest(unittest.makeSuite(SeaSurfTestCaseSave))
suite.addTest(unittest.makeSuite(SeaSurfTestCaseSetCookie))
suite.addTest(unittest.makeSuite(SeaSurfTestCaseReferer))
suite.addTest(unittest.makeSuite(SeaSurfTestManualValidation))
suite.addTest(unittest.makeSuite(SeaSurfTestCaseGenerateNewToken))

Flask-WTForms example doesn't make sense

I'm looking at the wtforms example:

class SeaSurfForm(Form):
    @staticmethod
    @app.before_request
    def add_csrf():
        csrf_name = app.config.get('CSRF_COOKIE_NAME', '_csrf_token')
        setattr(SeaSurfForm,
                csrf_name,
                HiddenField(default=getattr(g, csrf_name)))

Why would you set the default value of the hidden variable to the name of the cookie, as fetched from flask.g? That doesn't make sense.

docs/_build should not be in the repo

it only contains stuff that is generated from the rst docs.

having it in the repo makes the repo larger, changesets harder to read and also confuses github's statistics (it thinks this project has lots of javascript).

docs/_build should be added to .gitignore.

Traceback on any route

I cannot reproduce this one reliably as it only seems to happen on my production box.

Here's the traceback:

Traceback (most recent call last):
File "/home/projecto/venv/lib/python2.7/site-packages/flask/app.py", line 1836, in __call__
  return self.wsgi_app(environ, start_response)
File "/home/projecto/venv/lib/python2.7/site-packages/flask/app.py", line 1820, in wsgi_app
  response = self.make_response(self.handle_exception(e))
File "/home/projecto/venv/lib/python2.7/site-packages/flask/app.py", line 1403, in handle_exception
  reraise(exc_type, exc_value, tb)
File "/home/projecto/venv/lib/python2.7/site-packages/flask/app.py", line 1817, in wsgi_app
  response = self.full_dispatch_request()
File "/home/projecto/venv/lib/python2.7/site-packages/flask/app.py", line 1477, in full_dispatch_request
  rv = self.handle_user_exception(e)
File "/home/projecto/venv/lib/python2.7/site-packages/flask/app.py", line 1381, in handle_user_exception
  reraise(exc_type, exc_value, tb)
File "/home/projecto/venv/lib/python2.7/site-packages/flask/app.py", line 1473, in full_dispatch_request
  rv = self.preprocess_request()
File "/home/projecto/venv/lib/python2.7/site-packages/flask/app.py", line 1666, in preprocess_request
  rv = func()
File "/home/projecto/venv/lib/python2.7/site-packages/flask_seasurf.py", line 251, in _before_request
  if some_none or not safe_str_cmp(request_csrf_token, csrf_token):
File "/home/projecto/venv/lib/python2.7/site-packages/werkzeug/security.py", line 117, in safe_str_cmp
  return _builtin_safe_str_cmp(a, b)
TypeError: 'unicode' does not have the buffer interface

Updating documentation for specifying TESTING=True disabled token generation

I have a project with 30-odd files py files with multiple config files which are loaded as per the environment is set (like default + [DEV|UAT|PROD]). I was integrating CSRF with my application and it was just not working, i tried it for 1+ hrs and thought something is wrong in my app. i tried on another simple Flask app and it worked within a minute on both Python 2 & 3. I spent some time on google to look for reason behind generating "Empty" token but it was of no help!

Finally i decided to debug the code (which i should have done it first and within 5 minutes i discovered the code self._csrf_disable = app.config.get('CSRF_DISABLE', app.config.get('TESTING', False)) . i don't remember how or when this was set but after changing TESTING = False it worked. sadly i wasted around 2+ hours on this.

Please update the documentation to include warning / info, mentioning TESTING = True will disable generation of tokens and if it's not too much don't return silently on TESTING mode, add some warning like one below

# not properly indented!
def _before_request(self):
      if self._csrf_disable:
           import warning  # this would be at the top
           warnings.warn('Warning: Running application in TESTING mode, TESTING is set to True in config!')

referrer (spelling)

while we can't change the http header obviously (which is standardized on a incorrect spelling), we could at least spell referrer correctly at other places.

Add MANIFEST.in with link to LICENSE

I've put together a build of Flask-SeaSurf for conda-forge. (conda-forge/staged-recipes#1519) It'd be good to include a hard link to the license file in the build, but doing so requires that the license file be explicitly referenced in a MANIFEST.in file so that the license gets bundled in the source distribution.

Python 2.6 and seasurf 0.1.11

If you are running under python2.7, you will get the following error :

return str(hashlib.sha1('{}{}'.format(*salt)).hexdigest())
ValueError: zero length field name in forma

This is due to the fact that Flask-seasurf has been developped with python2.7 ?

Quoting stackoverflow (tinyurl.com/6r86x76) answer :

Python 2.6 requires the field numbers. In Python 2.7 and later, they can be omitted.

Changed in version 2.7: The positional argument specifiers can be omitted, so '{} {}' is equivalent to '{0} {1}'.

To fix the error edit the seasurf.py and the method _generate_token()

replace hashlib.sha1('{}{}'.format(_salt)).hexdigest() by hashlib.sha1('{0}{1}'.format(_salt)).hexdigest()

and line 187

self.app.logger.warning('Forbidden ({0}): {1}'.format(*error))

X-CSRFToken header not found in the request.headers dict

You mention in the documentation that you look for X-CSRFToken in the request headers however you are actually doing a request.headers.get('HTTP_X_CSRFTOKEN', '') which is not a valid header and consequently fails every time. 'HTTP_X_CSRFTOKEN' needs to be replaced with 'X-CSRFToken'.

I am trying out SeaSurf with Backbone.js and aside from this issue, works great!

Error with Flask-WTForms Usage

As per docs setting up flask app with WTForms it throws ImportError.

ImportError: cannot import name 'SeaSurfForm' from partially initialized module 'seasurf_form' (most likely due to a circular import) 

And HiddenField, TextField, PasswordField are part of wtforms package not under flask_wtf.
Also DataRequired, Email are inside wtforms.validators not under flask_wtf.

On wtforms,
https://github.com/wtforms/wtforms/blob/244c8d6b15accb3e2efd622241e5f7c1cc8abb9d/wtforms/form.py#L181-L200
Above code ignores attributes starting with '_'. So it'll ignore seasurf's csrf token field(CSRF_COOKIE_NAME) having default name '_csrf_token'

Option to disable referer checks?

Could an option be added to disable the referer check? I already have CSRF tokens attached to all forms and don't see the need to check the referer. I'd like to turn referrers off altogether with the new referrer policy meta tag but I can't due to seasurf checking them if the site uses https.

CSRF compatibility with Flask-WTF

WTForms (as of 1.x) and Flask-WTF have in-built CSRF protection. Is Flask-SeaSurf compatible with these, or should I disable one of them on views that handle WTForms? Will appreciate this being covered in the documentation.

strange token generation code

what is the point of that code (compared to just using a random value as csrf token)?:

    salt = (randrange(0, _MAX_CSRF_KEY), self._secret_key)
    return str(hashlib.sha1('%s%s' % salt).hexdigest())

(flask-csrf just uses str(uuid4()) btw.)

Disable CSRF when a request has no cookies.

CSRF protection only makes sense when a user is authenticated to an application with a session. Other scenarios (like when you use an API key) don't make sense to have CSRF enabled.

Possible change in _before_request():

if self._csrf_disable:
    return  # don't validate for testing

if not request.cookies:
    return 

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.