1

I am currently using the Python Flask framework's default session function to manage user login. My understanding is that, session function essentially is an encrypted cookie stored in a client's computer containing the information on which user has logged in etc.

Currently, I use

session.permanent = True

to make a user's login status permanent and use

app.permanent_session_lifetime = dt.timedelta(days=30)

to make it expire after 30 days. It works well. But my concern is, it seems to me that Flask only signs and encrypts the content of its session (i.e. the red part), but it does NOT sign and encrypt the expiration date of the cookie. My thought is that, suppose a browser is designed to ignore the "expires" section of a Set-Cookie header, it can simply send back a cookie which it knows has expired and my Flask app has no way to tell that the cookie is actually expired and let the corresponding user in rather than asking him to input the password again.

enter image description here

Is this a valid concern?

(Note: To be more certain, I tried to decrypted the session part manually and the content is a Python dictionary similar to the following: {'_permanent': True, 'dashboard': {'username': 'admin'}})

3
  • 4
    The Expires attribute is just for the browser. It can't be signed, and can't be relied upon for security. If you really want to make sure the cookie expires, you'd have to include the expiration date in the cookie content, and then check it server side when it is received.
    – nobody
    Jul 31, 2021 at 10:29
  • Hi, @nobody yes this is exactly what I am concerned. Seems that I have to write a timestamp into the session dict to be sure that I know when to kick out a user. However, Flask's official document does not make this clear. Therefore I am not sure if something has been built in so that I don't need to reinvent the wheel.
    – user129187
    Jul 31, 2021 at 10:34
  • If you want to know if flask has something built-in for this, that's a programming question and you should probably ask over at Stack Overflow
    – nobody
    Jul 31, 2021 at 10:40

2 Answers 2

2

There are two ways to check whether Flask does indeed do this:

  • You can take a look at the source code to see that they're using URLSafeTimedSerializer for forming the signed item, and TimedSerializer uses a TimestampSigner, which has the specific mitigation you're looking for; or
  • You can run a test application that shows you the data contained within a session:
from flask import Flask, session
import time
import datetime as dt

app = Flask(__name__)
app.secret_key = 'UnSecret!'
app.config['SESSION_REFRESH_EACH_REQUEST'] = False  # this is set so that the cookie isn't changed between requests, and a replay attack is possible.
app.permanent_session_lifetime = dt.timedelta(seconds=30)

@app.route('/set')
def set():
    session.permanent = True
    session['set_at'] = time.time()
    return 'Set cookies'

@app.route('/get')
def get():
    return f'Value: {session.get("set_at")}'

if __name__ == '__main__': app.run()

Then, in your favorite browser, open the network monitor and click "Copy as curl", then try performing this request using the same cookie that was served to you. The curl line will look like this:

curl 'http://127.0.0.1:5000/get' -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:90.0) Gecko/20100101 Firefox/90.0' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -H 'Accept-Language: en-US,en;q=0.5' --compressed -H 'DNT: 1' -H 'Connection: keep-alive' -H 'Cookie: session=eyJfcGVybWFuZW50Ijp0cnVlLCJzZXRfYXQiOjE2MjkwOTQ5NDMuMTg5MzM0Mn0.YRoEHw.M9vKqLiIT9DkmfEadGFo8JSl2V0' -H 'Upgrade-Insecure-Requests: 1' -H 'Sec-Fetch-Dest: document' -H 'Sec-Fetch-Mode: navigate' -H 'Sec-Fetch-Site: none' -H 'Sec-Fetch-User: ?1' -H 'Cache-Control: max-age=0'

What you'll find is that it keeps working before the time that the cookie expires, and starts displaying None after that time, even though the curl line doesn't contain anything about when the cookie expires.

You can perform this kind of test even on webapps you don't control, and the result proves whether or not their cookie format has protections against this.

2
  • So you mean that Flask is able to check the expiration of a cookie even if a browser does not respect the expires directive?
    – user129187
    Aug 16, 2021 at 7:15
  • Yes, I mean that. But don't take my word for it, test it in the way I've shown. The thing with using curl is specifically so that the cookie gets used despite your browser knowing it's expired -- because as far as I can tell, Firefox does not present a cookie to the server if it knows it's expired. The only way to test it within the browser appears to be to change the system clock, and I can't do that on my computer.
    – Danya02
    Aug 16, 2021 at 7:28
1

Session management - including expiration - is always the responsibility of the server. The client can't be trusted in any security decisions, except regarding the data that the client directly controls, and even then only to the extent that the client is not malicious. In other words, you can trust a legitimate browser to delete cookies from itself once they expire, but the browser can't delete cookies off the server or out of anywhere else that the browser doesn't control (such as an attacker's file of stolen cookie values). Similarly, a malicious client - or one merely intended for testing server security - might note when a cookie is expected to expire but not actually delete it when that time comes, specifically to see if the server will accept expired tokens.

For the server to handle session management correctly, it must have a trustworthy way to identify valid vs. expired tokens. If tokens are stored in server-side state (in RAM, an external cache, a database, etc.) then each token needs to be stored with a validity period, which is checked whenever the token is retrieved (most web app frameworks do this for you automatically). If tokens are stateless (that is, they are not stored in any persistent form on the server) then they must both include their own validity period, and a cryptographic signature/MAC (which is NOT the same thing as being encrypted; encryption by itself does not prove authenticity) to prevent the token from being forged or tampered with. This is how JWTs work.

You must log in to answer this question.