I'm currently writing a simple blog application using Flask-Admin for administrative tasks. I'd like to enable CSRF protection and have followed the instructions on the Advanced Functionality page in the docs.
However, this doesn't seem to prevent my dummy CSRF attacks from succeeding and it is unclear how Flask-Admin follows the CSRF protection implementation outlined in the WTForms documentation.
Regarding my dummy CSRF attacks, my method follows the example for POST requests suggested in the OWASP CSRF article:
<body onload="document.forms[0].submit()">
<form action="http://localhost:5000/admin/blogpost/new?url=%2Fadmin%2Fblogpost%2F" method="POST">
<input type="hidden" name="title" value="Evil title"/>
<input type="hidden" name="content" value="Evil content"/>
<input type="submit" value="Save"/>
</form>
where the action address is the new post creation page that draws from the create.html template of Flask-Admin.
When I load the malicious form from a separate origin (localhost:3000) and submit it on document load, I find that the request goes through despite the form being configured with the Flask-Admin SecureForm setup linked above.
I've tried consulting the Flask-Admin source code, but couldn't find the WTForms csrf_token in the relevant templates, though the SecureForm includes the CSRF settings as expected.
Does Flask-Admin diverge from the documented WTForms implementation of CSRF protection? If so, how?
More practically speaking, is there any additional configuration beyond the use of Flask-Admin's SecureForm in my ModelView-based classes necessary to enable CSRF protection?
Note also that the technique described in this answer also worked for me in my attempts to use exposed admin URLs cross-site (again from localhost:3000) to perform actions requiring authentication.
This leads me to ask the follow-up: is it possible to implement CSRF on non-form Flask-Admin pages, i.e. for GET requests, to protect exposed admin URLs used for operations such as logout or account deletion operations?
Related
I have a very simple app that has no user management or any Flask-Login auth needs. It has forms, WTForms. All I want to do is collect some data submitted by the form. I could technically disable CSRF validation but Flask WTForms really urges me not to.
I'd like to disable flask session cookie in the browser because it seems unnecessary and I would need to put a cookie banner for GDPR compliance. So to avoid all that, I thought of disabling flask session cookie as follows:
class CustomSessionInterface(SecureCookieSessionInterface):
""" Disable session cookies """
def should_set_cookie(self, app: "Flask", session: SessionMixin) -> bool:
return False
# App initialization
app = Flask(__name__)
app.session_interface = CustomSessionInterface()
But doing so leads to a 500 error: "The CSRF session token is missing". However, looking at the HTML that was rendered has the following csrf token rendered properly:
<input id="csrf_token" name="csrf_token" type="hidden" value="ImI2ZDIwMDUxMDNmOGM3ZDFlMTI4ZTIzODE4ODBmNDUwNWU3ZmMzM2Ui.YhA2kQ.UnIHwlR1qLL61N9_30lDKngxLlM">
Questions:
What is the relationship between CSRF token validation and session cookie? Why is a cookie necessary to validated the CSRF token?
I tried enabling session cookies again, deleting the cookie in Chrome developer tools leads to the same error. So, indeed, session cookie seems to be absolutely necessary to validate CSRF token.
How can I use CSRF form validation without a session cookie?
Thank you so much.
I found out from the code base of WTForms: https://github.com/wtforms/flask-wtf/blob/565a63d9b33bf6eb141839f03f0032c03894d866/src/flask_wtf/csrf.py#L56
Basically, session['csrf_token'] is stored in the session and compared against the form.hidden() tag (or form.csrf_token) in the HTML body.
This is not clearly explained in the docs. But the codebase makes it clear. I guess there is no way to do CSRF protection without secure cookies.
The downside of this is that you can't get rid of cookies. I suspect, one could build a server-side session database, but then there are issues with scaling your Flask app horizontally.
You should check if it helps you:
https://wtforms.readthedocs.io/en/3.0.x/csrf/#creating-your-own-csrf-implementation
This allows you to create your own implementation of csrf token generation and validation in which you do not need to use cookies.
I have an html form, and I would like to insure that all submissions come from my website. I think I have seen people using a key for this (I believe this happens in Django?), and might have some ideas on how to go with that. Is there any standard way to do this in Flask?
Edit:
Now I know I'm talking about CSRF token middleware. Again, is there any standard way of doing this in Flask? How can I store the key on the server side?
In flask you can do CSRF protection using Flask-SeaSurf.There are other methods also but it is straight forward.
To start Just do pip install flask-seasurf and you are ready
import Flask
from flask_seasurf import SeaSurf
app = Flask(__name__)
csrf = SeaSurf(app)
<form method="POST">
...
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
</form>
#csrf.exempt
#app.route('/exempt_view', methods=['POST'])
def exempt_view():
'''This view is exempted from CSRF validation.'''
return 'foobar'
For more information you can visit official website
Please mark this as answer if this solves you problem.
I am using django-allauth for a website. I have integrated Google login through it. Currently, when I access the login, I get the standard sign-in page, on which is a link to sign in through Google. What is the best way to skip the standard sign-in page and go straight to the Google one (as in the one on accounts.google.com...)? All login is done through Google, so I don't need to see the initial page, just the Google one.
Should I override the provided template to just redirect? Or is there a better way to configure it?
I know it is a old topic, but I had the same issue and could not find a Solution on the web because of that I want to share my best solution. First of all I created a new socialaccounts template, so that this one will be taken instead of the original allauth template. For that you need to create a new folder and html file as followed:
"template->socialaccount-> login.html"
Thats the page you want to skip. To do that I simply inserted a javascript which, submits the form as soon the page is loaded:
{%load socialaccount%}
<body onload="document.forms['google_login'].submit()">
<p>You should be redirect to the Google login page in some seconds otherwise use the button below.</p>
<form method="post" name= "google_login">
{% csrf_token %}
<button type="submit">Continue</button>
</form>
</body>
Maybe this helps someone else.
Best regards
I just copied the address to which the existing Django-template hyperlink pointed, and then created a view to redirect all logins to that link with the correct return page. My URL pattern:
url(r'^login/$', views.redirectToGoogle, name="rediect_to_Google")
My view was:
GOOGLE_LOGIN_URL_PREFIX = '/accounts/google/login/?'
def redirectToGoogle(request):
coming_from = request.GET.get("next", "/manage")
url_params = {
"process": "login",
"next": coming_from
}
suffix = urllib.parse.urlencode(url_params)
return redirect(GOOGLE_LOGIN_URL_PREFIX + suffix)
I'm not sure if this is even possible, but I would like to grab a user's input, pull it into my views.py, manipulate it, and then use that data in other views.
I do not need this data stored in a database, as I won't be referencing it again, and I want to keep this as lightweight as possible.
Currently, I'm trying to pull data from espn's fantasy football site using the python library espnff. My homepage consists of a textfield box and a submit button (Think of google.com).
I have functions set up that will comb through an espn url such as http://games.espn.com/ffl/clubhouse?leagueId=123456 to grab the leagueID, from there I make use of espnff to grab more info on that league.
My ideal use case is someone comes to my site, copies and pastes their league url like the one above, clicks submit and then brings them to https://example.com/{{ leagueID}/ which will display different info that I gather.
I have not found a way to do this without submitting the user input to a model. Is possible to avoid using the database? If so how?
Not sure I understood it right, but what you are trying to do can easily be done without using any models/database or any other kind of persistent storage.
The user submits that information using the form, you grab the URL from the request object in your view, parse the URL to get the league_id and then redirect the user to /{league_id}.
Then on that view, you gather the league_id parameter (from the url), use the library (espnff) to fetch the data with that id and then render the template with that data.
For example, the implementation would be something in these lines:
Make a form in your html template:
<form method="post" action="/">
{% csrf_token %}
<input type="text" name="league_url"/>
<input type="submit" value="Submit" />
</form>
in urls.py:
url(r'^$', index_view, name="index"),
url(r'^(?P<league_id>[0-9]+)$', league_view, name="league_view")
in views.py:
def index_view(request):
if request.method == 'POST':
league_url = request.POST.get('league_url', None)
# Your code to parse the URL and extract the ID
return HttpResponseRedirect('/{}'.format(league_id))
else:
# render form template
def league_view(request, league_id):
# your code here using the league_id
# and render the page with data
(I didn't tested that code, I just wrote it quickly as an example of the flow)
The django documentation describes quite extensively how to do caching with django. You can find the documentation on how to set that up here
Once it's been set up you simply use the cache in the following way
from django.core.cache import cache
cache.set('my_key', 'my_value', 60) # number is in seconds
value = cache.get('my_key')
You can provide dictionaries and such as values. The caching framework will serialize that for you using pickle.
I have a question regarding Django Forms and GET
I have a form to download student scores in CSV format. The fields are name and year so I have a forms.py
StudentDownloadForm(forms.Form):
name=forms.CharField()
year = forms.CharField()
And I want to use this form in the template.html with
context={'student_form' : StudentDownloadForm(),}
<form action ="" method="GET">
{% csrf_token %}{{ student_form|crispy }}
<input type="submit" value="Query"/>
</form>
So my questions are as follows:
If I use the method="GET" then the csrf token is visible in the URL, which is a security issue
Can I then use the method="POST" instead?
Alternatively, can I remove the csrf token in the form?
According to Django documentation (Cross Site Request Forgery protection):
For all incoming requests that are not using HTTP GET, HEAD, OPTIONS
or TRACE, a CSRF cookie must be present, and the ‘csrfmiddlewaretoken’
field must be present and correct. If it isn’t, the user will get a
403 error.
And:
It deliberately ignores GET requests (and other requests that are
defined as ‘safe’ by RFC 2616). These requests ought never to have any
potentially dangerous side effects , and so a CSRF attack with a GET
request ought to be harmless. RFC 2616 defines POST, PUT and DELETE as
‘unsafe’, and all other methods are assumed to be unsafe, for maximum
protection.
So, you can omit CSRF token for GET requiests