Django session cookie lost because of multiple concurrent requests - python

I'm building an application in which the client pings the server every now and then (let's not get into why). When the server handles these requests, it checks whether the client is logged in or not using request.user.is_authenticated()
It looks something like this:
def handle_ping_request(request):
if request.user.is_authenticated():
# Do something...
else:
# Do Something else...
I've noticed that sometimes the server receives a log-in request immediately followed by a ping request (from the same user). The client is then successfully logged-in, the response returns with a new session ID (of the logged in user) and (I guess that) the old session-ID (of the anonymous user) is removed. When the ping request is processed, its request contains the old session-ID. Thus the ping request returns with a third session ID and on the next request the client makes, the client is no longer logged in.
My log-in code looks something like:
if not request.user.is_authenticated():
user = auth.authenticate(...credentials...)
if user and user.is_active:
auth.login(request, user)
Do you have any suggestions on how to avoid this problem? Preferably without involving the client.
Thanks.

It's probably too messy to handle this on the server because you'll have to create some kind of a semaphore system that will also try to guess if any ping is currently from a client that is also being authenticated. My suggestion would be simply to change the client code not to ping while it's waiting for a response to its login request.

You could create an alternative to the standard contrib.auth.login method that keeps the same session id, rather than generating a new one, either by using a custom authentication backend that doesn't generate a new key, or by creating a custom session backend that overrides the cycle_key() method in contrib.sessions.base to reuse the same key.
BUT: think about what you might be opening yourself up to by reusing the same session key - depending on where this system is in use, you'd be making yourself more open to session hijacking (ie: there's only one session id to sniff), as well as potentially issues where caches may return the unauth page content instead of the auth page content because the sessionid is technically the same and the cache can't tell the difference between the two situations, etc, etc, etc.
In short: there's a reason it's built to work, by default, the way it does.

Related

Flask post/redirect/get pattern cannot recover from an error

I have an error recovery problem in a Flask view function. Its simplified version is here:
#app.route('/log', methods=['POST', 'GET'])
def elog():
form = LogForm()
if form.validate_on_submit():
flask.session['logformdata'] = form.data
return flask.redirect(flask.url_for('elog'))
try:
formdata = flask.session.pop('logformdata')
except KeyError:
return flask.render_template('log.html', form=form)
log = ... # result of a query
form.process(data=formdata)
return flask.render_template('log.html', form=form, log=log)
The important part is that it implements the post/redirect/get pattern and stores the form data between POST and GET in the flask.session storage which is implemented with HTTP cookies.
Now let's assume there is a bug somewhere and the function crashes with certain input. Of course, it should not happen. But when it does, the user's experience is terrible.
In detail:
the user posts a form (POST)
the form data is stored in the flask.session, i.e. as a cookie
after a redirect, the function is called again (GET), but now it crashes unexpectedly. The user sees some error message. That is not good, but bugs happen.
the user reloads the page intending to start over, but gets the same error repeated again and again!
The key point is that the statement flask.session.pop removes the form data from the session storage, but when the function crashes, the corresponding cookie remains in the user's browser. Each reload triggers the bug again. Restarting the browser may help (depending on session.permanent flag). The only guaranteed remedy is to manually delete the cookie from the browser. This effectively makes the webpage unusable.
I think I can mitigate the problem with setting a very short cookie lifetime (15 seconds or so) or by generating a new secret key after each restart. I did not try it and it is definitely not a good solution if session cookie contains other data.
How can I make functions like the one above more robust?
If there's no way to handle the error within the view itself, my approach would be to register an error handler for the entire app, and manage the session within here i.e. reset to a default/safe state.
Example:
#app.errorhandler(500)
def handle_internal_server_error(ex):
"""Handle an unexpected and uncaught (500) server error."""
# Reset session here & display error (e.g. flash or other)
return # redirect or render
I think your simplified example misses an important bit representing what exactly crashes.
If some piece of the logic can crash unexpectedly you should expect for it to do so and handle these cases. For example, by wrapping it in a usual try/catch and removing the session cookie within the catch block.
You are managing the session manually and that is causing this issue. Please look into implementing Message Flashing in your application. You can customize flashes to use server side session or even use cookies. Flash messages can be maintained across multiple pages even with redirects as well.

How to limit one session from any browser for a username in flask?

I am using a gunicorn server in which I am trying to figure out a way to limit only one session per username i.e. if user A is logged in to the app from Chrome he should not be able to login through Firefox unless he logs out of chrome, or shouldn`t be able to open another TAB in chrome itself.
How can I generate a unique id for the browser and store it in a DB so that until the user logs out or session expires, the user can`t login through any other browser.
A possible method of limiting sessions to a single tab involves creating a random token on page load and embedding this token into the page. This most recently generated token gets stored in the user's session as well. This will be similar to how various frameworks add validation tokens to prevent CSFR attacks.
Brief example:
User loads page in tab 1 in Firefox. Token1 is generated, embedded and stored in session
User loads page in tab 2 in Firefox. Token2 is generated, embedded and stored in session. This overwrites previous value.
User loads page in tab 1 in Chrome. Token3 is generated, embedded and stored in session. this overwrites previous value.
At this point, the user has the page open in 3 tabs. The user's session, though, only has Token3 stored. This method prevents the user from being locked out (different IP addresses, different user agent strings, incogneto mode, etc) because each new session simply generates a new token. The newest load becomes the active window, immediately invalidating all previous sessions.
Next, any time the page interacts with the server (clicks a link, submits data, etc.), the token embedded in the page is sent as well. The server validates that the passed token matches the token in the session. If they match, the action succeeds. If they do not match, the server returns a failure message.
You can generate random numbers in multiple ways, but you probably want something secure. We'll use the example from another question:
import string
import random
...
N = 20 # Length of the string we want, adjust as appropriate
''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(N))
This uses random.SystemRandom, which is more secure than simply using random.choice
On page load, you need to check if the existing token is valid, generate the random token and store this in the user's session. Since we want this everywhere, let's make a decorator first, to reduce duplicate code later. The decorator checks if the session is valid and if not you get to select what to do (insert your own logic). It also sets a session token. This is needed (or you need logic to exclude your main page) otherwise you'll hit an infinite loop where the user attempts to load the main page, doesn't have a token, fails and the process repeats. I have the token regenerating on each page load via the else clause. If you do not implement the if section, this decorator is pointless as both paths perform the same action and simply reset the token on page load. The logic in the if is what will prevent the user from having multiple sessions.
from flask import session
from functools import wraps
def random_string(length):
return ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(length))
def validate_token(f):
#wraps(f)
def wrapper(existing_token, *args, **kwargs):
if session['token'] != existing_token:
# Logic on failure. Do you present a 404, do you bounce them back to your main page, do you do something else?
# It is IMPORTANT that you determine and implement this logic
# otherwise the decorator simply changes the token (and behaves the same way as the else block).
session['token'] = random_string(20)
else:
session['token'] = random_string(20)
return f(*args, **kwargs)
return wrapper
Now in our routes, we can apply this decorator to each, so that the user session gets updated on each page load:
from flask import render_template
#app.route('/path')
#validate_token
def path(token=None):
return render_template('path.html', token=session['token'])
In your template, you want to utilize this token value anywhere you need to prevent the session from continuing. For example, put it on links, in forms (though Flask has a method of CSRF protection already), etc. The server itself can check if the passed token is valid. The template could look as simple as this:
Click here to continue
I don't know how to implement it exactly, but it seems that you need websockets (See here)
This way, on every page load, you could have an Ajax request to the server, which would use the websocket functionality to query the previously opened browser+tab, if any. If it has an answer, it means that there is another browser+tab open. The server should then should return this information.
Your script which called the server through Ajax, depending on the return, should then decide to redirect to an error page, or to continue loading it.
I never used Websocket, but I'm pretty confident this would work (as is is well implemented in most browsers now).

Flask security issue with session and request repeater

I'm using Flask builtin session mecanism.
Here is my understanding of session mecanism (with flask) :
all session data are stored in a signed cookie (with app.secret_key)
when a session data is modified, the cookie is changed
session's data are protected against write client side (due to signature) but not against read
Imagine the following scenario :
In my session I put a variable try_number=3
Each time the user make an action, a decrease this number
If this number is equal to 0, action is forbidden
The user connect to the application for the first time, the application send a Set-Cookie: sesssion=Flask.sign("try_number=3"), let's call this cookie COOKIE_A.
The user perform his first action, he send COOKIE_A, the application reply with Set-Cookie: sesssion=Flask.sign("try_number=2"), let's call this cookie COOKIE_B.
Now, if the user perform another action, but doesn't use COOKIE_B but COOKIE_A again (using curl for exemple), the cookie is still signed, and will be handled by the server, with try_number=3.
Therefore, only using the COOKIE_A for all operation, he will be able to "spoof" session mecanism, and make unlimited action with the same session.
Is there any builtin mecanism to prevent this ?
(I'm not talking about snippet for using sqlite / redis, but builtin solution)
This is not a failure of the security of Flask's cookies, it's a failure of your counter design. There is no built in protection against replay attacks.
You can shorten the expiration time of the session cookie. This doesn't really solve the problem, it just makes the window smaller. It also makes the session inconvenient for regular use, which would annoy your normal users.
Ultimately, you'll have to store some information on the server and check against it. You could send a nonce with every request and keep a store of which ones have been sent back, ignoring ones that have been seen before. You could also just store all session information (except some identifying key) on the server side, so it can't be re-sent.

Somewhat confused about how auth and sessions work in Python/Django

I'm using an existing database for my latest Django project, so unless I change my Models or the Django auth code, it's going to be rather difficult to merge the two.
Rather than messing with the existing auth backend, I'm planning on just writing my own authentication app.
Anyway, all of my previous authentication apps have been written in PHP, where basically i just throw everything in session variables and verify them on every page... Here's what I'm a bit confused about. It appears that when a user is authenticated/logged in, the entire user is added to a session, but I can't figure out where or how that is occurring.
In the default Django login function, it's assigning user to request.user ... is this being saved as a session variable somehow or is it just passed into the next view? If it is just being passed to the next view, how are future requests authenticated without requiring further login requests?
The default Django auth login is below..
def login(request, user):
"""
Persist a user id and a backend in the request. This way a user doesn't
have to reauthenticate on every request.
"""
if user is None:
user = request.user
# TODO: It would be nice to support different login methods, like signed cookies.
if SESSION_KEY in request.session:
if request.session[SESSION_KEY] != user.id:
# To avoid reusing another user's session, create a new, empty
# session if the existing session corresponds to a different
# authenticated user.
request.session.flush()
else:
request.session.cycle_key()
request.session[SESSION_KEY] = user.id
request.session[BACKEND_SESSION_KEY] = user.backend
if hasattr(request, 'user'):
request.user = user
user_logged_in.send(sender=user.__class__, request=request, user=user)
I also tried to follow the user_logged_in.send(), which is in django.dispatch.dispatcher.send but I'm not entirely sure what that's supposed to do either.
def send(self, sender, **named):
"""
Send signal from sender to all connected receivers.
If any receiver raises an error, the error propagates back through send,
terminating the dispatch loop, so it is quite possible to not have all
receivers called if a raises an error.
Arguments:
sender
The sender of the signal Either a specific object or None.
named
Named arguments which will be passed to receivers.
Returns a list of tuple pairs [(receiver, response), ... ].
"""
responses = []
if not self.receivers:
return responses
for receiver in self._live_receivers(_make_id(sender)):
response = receiver(signal=self, sender=sender, **named)
responses.append((receiver, response))
return responses
Basically what I'm looking for is for someone to explain an efficient way to save user session data in Python that does not depend on the Django framework. A little run-through of the Django authentication would be nice as well.
HTTP is stateless; regardless of the server used, the framework or language, there is no intrinsic way for an HTTP client to say "this request is part of that session". That's part of the design of HTTP.
So sessions are always a feature of the web application; either supported by the a web app framework or implemented in the app itself. The most usual way for a stateful session to be created from the stateless protocol is with cookies; Clients will store cookies at the request of a server and return those same cookies to that server in future requests.
Session data can be serialized and stored in the cookie itself, but that's both insecure (secret information could be forged or eavesdropped), and inefficient (takes bandwidth even though the individual bytes are of no use to the client), and so the preferred solution is to use an opaque (and even better, single use) session key is stored in a cookie, and the web application will store session data out of band; either in memory, in the filesystem, or a database backend, or some other option.
django takes care of most of this transparently in "middleware", modules that modify incoming requests and outgoing responses. The auth middleware will read a cookie and check if that represents a logged in user, and if so, add a user object to the request; it also attaches cookies to responses when a user gets logged in. the session middlware works in a similar fashion, checking for a cookie, and reading the session data from wherever it was stored between requests, and also grabbing session data from responses and storing them, along with setting a cookie to associate the client's session with the session data it just stored.
Since both of these features are useful, independent of each other (I tend to avoid sessions, but usually use some kind of authentication), they do not depend on each other. Session-like authentication is implemented in a similar manner as sessions, but authenticated users are not stored in "The Session", nor are sessions attached to "The authenticated User".
You might not think so, but django's authentication system is designed to be extended; if you already have a database of valid users you'd like to authenticate against, it's very simple to add a new auth backend that dovetails neatly into the standard django auth application (which means you can also use other applications that depend on it in turn).

What can I attach to pylons.request in Pylons?

I want keep track of a unique identifier for each browser that connects to my web application (that is written in Pylons.) I keep a cookie on the client to keep track of this, but if the cookie isn't present, then I want to generate a new unique identifier that will be sent back to the client with the response, but I also may want to access this value from other code used to generate the response.
Is attaching this value to pylons.request safe? Or do I need to do something like use threading_local to make a thread local that I reset when each new request is handled?
Why do you want a unique identifier? Basically every visitor already gets a unique identifier, his Session. Beaker, Pylons session and caching middleware, does all the work and tracks visitors, usually with a Session cookie. So don't care about tracking users, just use the Session for what it's made for, to store whatever user specific stuff you have .
from pylons import session
session["something"] = whatever()
session.save()
# somewhen later
something = session["something"]
Whatever you were to set on the request will only survive for the duration of the request. the problem you are describing is more appropriately handled with a Session as TCH4k has said. It's already enabled in the middleware, so go ahead.

Categories

Resources