I am using Django's signed cookie engine for storing session data. My settings are:
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
SESSION_COOKIE_NAME = 'sessionid'
And it works as it should. Even after closing browser and web server when I visit the website, I am still logged in. I can see the sessionid being set but I can't seem to find where session data is stored. I am storing entire user object in session.
The data is stored in the sessionid itself. Here's the relevant source code from the signed cookie backend:
def _get_session_key(self):
"""
Instead of generating a random string, generate a secure url-safe
base64-encoded string of data as our session key.
"""
return signing.dumps(
self._session, compress=True,
salt='django.contrib.sessions.backends.signed_cookies',
serializer=self.serializer,
)
Related
I have a server-side session file created and I am new to web applications. I don't understand why the session files when opened with text file has plain content inside it. I have a secret key setup and all but why is it not encrypted?
from flask import Flask, render_template, request, redirect, url_for, session, flash
from flask_sessions import Session
app = Flask(__name__)
app.config['SECRET_KEY'] = 'keykeykey'
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
app.config['SESSION_USE_SIGNER'] = True
server_session = Session(app)
And on login the file route is
app.route('/login', methods=['GET', 'POST'])
def login_page():
session['email'] = email
return redirect(url_for('home_page'))
And on logout the route is
#app.route("/logout")
def logout():
session.pop('email', None)
return redirect(url_for("home_page"))
WHen the session is started a file is created in dir/flask-sessions/2029240f6d1128be89ddc32729463129, there are two files generated for each time and when I open it with notepad I can see the email id in plain text that is
Mø`.€•i }”(Œ
_permanent”ˆŒ
csrf_token”Œ(fb90d22be1adc1237c52730fadf95d1e07936cdd9e”Œemail”Œemail#email.com”u.
the ending email#email.com is the input from the form.
My questions are
Why is the content not encrypted even though it is stored in my server?
When I do session.pop() why is the file not deleted?
EDIT:
I guess the issue is because I use from cachelib import FileSystemCache instead of from werkzeug.contrib.cache import FileSystemCache?? Is that the issue? How can I overcome this as latest version of werkzeug doesn't have .contrib?
Trying to answer it to the best of my knowledge.
1) Why is the content not encrypted?
You do not really need to worry about the session stored in your server as long as your server is secured. The vulnerability is the session stored as cookies in the browser. To bypass that, the 'SECRET_KEY' is used to let the server sign the session variables before storing them in the browser. That is the reason why you might still see the session in plain text on the server. It will be signed in the browser cookie-data though.
2) When I do session.pop() why is the file not deleted?
To understand what the session.pop does, I did a little exercise.
At first, my flask session looked like this:
Session is: <SecureCookieSession {'id': '27260b14-405d-440a-9e38-daa32d9a7797', 'loggedin': True, 'username': 'Rajat Yadav'}>
When I pop all the keys in the session dict mapping, I am left with this:
New Session is: <SecureCookieSession {}>
The clarity is that the key:value pair gets deleted as we pop the session. One thing for sure is that pop does not delete the complete dictinary object but just the key:value pair inside.
To your question of the file not getting deleted, I believe deleting the dictionary object should do the trick.
Try:
del session
Let me know if this deletes the file.
I have a question about flasks session logic. First of all as I know there are two ways to store session data, one is on client side and the second is server side. Flask, as I know, is using the former one (client side) where the session is encrypted and stored on the browser of the client.
Let's us say we want to make a login on a flask-backend
User does a login, flask generates a session and through set-cookie the client stores the session
User makes another request to the backend and sends its cookies where also the session is stored and flask validates the session with the key which it used to encrypt the session
When the session is valid, flask loads the session, thus that means the user is logged in
And JWT works the following as I know. It generates a token and the client stores the token and sends it in each request to the server, where the token is validated.
As I know, both flask and JWT uses a secret to encrypt the data.
So can we say, that flask-session and JWT are somehow similar?
Both jwt and flask session work on client, but the key difference is that flask session stores the signed session data at the client cookie, but in jwt you have the independence to store the token anywhere you want, say localstorage, cookie etc.
And jwt will be base64 encoded by default while in flask session it has to be done manually for security. But yes the difference between traditional sessions (say php) and flask-sessions is that the session data is stored in the client rather than as a file at the server (while the client cookie has session id in traditional sessions).
I'm trying to put some security on my Flask web app. As a first step I'm going to make my session cookie secure by setting SESSION_COOKIE_SECURE to true.
But after I get my session cookie from "inspect element" I can decode session cookie easily and there is no difference whether I add SESSION_COOKIE_SECURE or not.
Here is my code:
from flask import Flask, request, app, render_template, session, send_file, redirect
MyApp = Flask(__name__)
MyApp.secret_key = "something"
application = MyApp
if __name__ == "__main__":
MyApp.debug = False
MyApp.config.update(
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE='Lax',
)
MyApp.config["SESSION_PERMANENT"] = True
MyApp.run()
I also tried to add this attribute using the following syntax but this made no difference:
MyApp.config['SESSION_COOKIE_SECURE'] = True
When I try to print SESSION_COOKIE_SECURE I get this error
Traceback (most recent call last):
File "...", line ..., in <module>
print(MyApp.session_cookie_secure)
AttributeError: 'Flask' object has no attribute 'session_cookie_secure'
My Flask version is 1.0.2, and I'm on HTTPS.
Setting SESSION_COOKIE_SECURE does not encrypt the cookie value, no. When set, this causes Flask to create cookies with the "Secure" flag set. This means that a browser can only return the cookie to the server over an encrypted connection, nothing more. The setting doesn't change anything about the cookie value itself.
Flask produces cookies that are cryptographically signed, by default. That means that the cookie contents can be decoded but not altered, because a third party without access to the server secret can't create a valid signature for the cookie.
You generally don't need to encrypt your session cookie if you a) use HTTPS (which encrypts the data from outsiders) and b) protect your web app from XSS attacks. Without an XSS attack vector, attackers can't get access to your cookie contents at all anyway.
You certainly don't need to do so here, as SESSION_COOKIE_HTTPONLY means that the browser will never expose the cookie to JavaScript, and only someone with full access to the browser can see the cookie value.
Flask doesn't have a 'encrypt cookie' setting, because it is not deemed necessary when you can secure the cookie in other ways. You should not store information in a session cookie so sensitive that it should be protected from the end-user with access to the browser storage; keep such data on the server and only store a unique identifier in the session to retrieve that secret data later on.
If for some reason you can't keep such secrets out of the session cookie and are unwilling to accept that the end-user can read this data, then you'll have to encrypt the cookie yourself or use an alternative session provider for Flask, such as EncryptedSession.
As for the attribute error: only a few configuration settings are accessible as attributes on the Flask object. To print arbitrary configuration settings, use the app.config object:
print(MyApp.config['SESSION_COOKIE_SECURE'])
There is my code:
class WebHandler(RequestHandler):
def get(self):
self.set_cookie('name1', 'value.1')
self.set_cookie('name2', 'value.2')
self.write('OK')
When I run this code, chrome browser doesn't set cookie 'name1' but still set cookie 'name2'. When I remove dot character in value of cookies, it's oke. How do I set multiple cookies with dot character in value?
From my point of view, it is possible that the question is a bit incomplete, but I will still try to answer it in the hope that it will help you and others.
Tornado’s set_secure_cookie() and get_secure_cookie() functions send and retrieve browser cookies that are protected against malicious modifications in the browser. To use these functions, you must specify the cookie_secret parameter in the application constructor. Let’s look at a simple example.
The application will render a page that counts how many times it has been reloaded in the browser. If no cookie has been set (or if the cookie has been tampered with), the application will set a new cookie with the value 1. Otherwise, the application will increment the value read from the cookie.
import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.options
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class MainHandler(tornado.web.RequestHandler):
def get(self):
cookie = self.get_secure_cookie("count")
count = int(cookie) + 1 if cookie else 1
countString = "1 time" if count == 1 else "{} times".format(count)
self.set_secure_cookie("count", str(count))
self.write(
"""
<html><head><title>Cookie Counter</title></head>
<body><h1>You’ve viewed this page {} times.</h1>
</body></html>
""".format(
countString
)
)
if __name__ == "__main__":
tornado.options.parse_command_line()
settings = {
"cookie_secret": "u5SXVuerTfyQTT7uTbu7HjqiqHnh8UsBm37J4Y5lwto="
}
application = tornado.web.Application([(r"/", MainHandler)], **settings)
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(options.port)
try:
tornado.ioloop.IOLoop.instance().start()
except KeyboardInterrupt:
print("Server has shut down.")
If you inspect the value of the cookie in the browser, you will notice that the value stored for count is "count=\"2|1:0|10:1612910394|5:count|4:MQ==|e8c35def2daaec8da8ca5f3f1db63168f97027024a824d17b5e405f4f97c26ce\"". Tornado encodes the cookie value as a Base-64 string and appends a timestamp and an HMAC signature to the cookie contents. If the cookie’s timestamp is too old (or from the future), or if the signature doesn’t match the expected value, the get_secure_cookie() function assumes the cookie has been tampered with and will return None, as if the cookie had not been set.
The cookie_secret value passed to the Application constructor should be a unique, random string. Executing the following code snippet in a Python shell will generate one for you:
>>> import base64, uuid
>>> base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)
b'KeQWrXgiTjWQIEzcRbx0vV/IM/mYAEqvs+EtZ/5dvfs='
Tornado’s secure cookies are still susceptible to snooping, however. Attackers may be able to intercept cookies via scripts or plug ins in the browser, or simply by eavesdropping unencrypted network data. Remember that cookie values are signed rather than encrypted. Malicious programs are able to read stored cookies and either transmit their data to arbitrary servers or forge requests by sending them unmodified to the application. Therefore, it’s important to avoid storing sensitive user data in a browser cookie.
We also need to be aware of the possibility that a user could modify his own cookies, which could lead to a privilege escalation attack. If, for example, we store the number of remaining articles a user has paid to view in a cookie, we would want to prevent the user from updating that number himself in an attempt to get free content. The httponly and secure cookie properties can help prevent these sorts of attacks.
Setting the secure attribute on a cookie instructs the browser to transfer the cookie only over SSL connections. (It’s a little confusing, but this is not the same as Tornado’s secure cookies, which are more accurately described as signed cookies.) Since Python version 2.6, the Cookie object also supports the httponly attribute. Including this attribute instructs the browser to make the cookie inaccessible to JavaScript, which can prevent cross-site scripting attacks from reading the cookie’s value.
To enable these features, you can pass keyword arguments to the set_cookie and
set_secure_cookie methods. For example, a secure, HTTP-only cookie (that’s not
signed by Tornado) could be sent with the call:
self.set_cookie('foo', 'bar', httponly=True, secure=True)
Since the Tornado comes with built-in XSRF protection. For more details on this, see the official documentation, you can also see one of my answers in which it is set "xsrf_cookies": True.
I'm struggling to figure this one out, sessions work when i run my application normally but i can't figure out how to set data in the session in my test case.
The docs say in a test case you have to save the session to apply the changes before making the request. https://docs.djangoproject.com/en/1.2/topics/testing/#persistent-state
e.g.
from django.test import TestCase
class TestLogin(TestCase):
def test_processuser(self):
redirect = '/processuser/'
session = self.client.session
session["id"] = '1234'
session.save()
response = self.client.get(redirect)
However the session object returned from self.client.session is just a normal python dict?
Diging into the code the Client.session call is this:
def _session(self):
"""
Obtains the current session variables.
"""
if 'django.contrib.sessions' in settings.INSTALLED_APPS:
engine = import_module(settings.SESSION_ENGINE)
cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
if cookie:
return engine.SessionStore(cookie.value)
return {}
session = property(_session)
cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None) returns None so it just returns a dict in stead of a session store.
It looks like i have to do some more preparation in the test client before i save a session? Not really got much experience in this any help would be appreciated.
Django 1.2.5
Python 2.6.5
Cheers,
Asim.
Edit: this answer is now outdated; as of at least Django 1.7, you can just set the cookie directly on the test client.
See e.g. this answer to this question or the comments on this answer to another, similar, question.
Old outdated answer follows...
Adding this for people who really do need to set a cookie, e.g. because they need to do something which isn't covered by the Django auth mechanism...
You can't set cookies directly on TestClient objects but if you use the RequestFactory class you can do it. So instead of (say):
response = Client().post('/foo')
you do:
request = RequestFactory().post('/foo')
request.COOKIES['blah'] = 'hello'
response = foo_view(request)
where foo_view is the view corresponding to the '/foo' path, i.e. the view you're looking to test.
HTH somebody.
The simplest thing would be to login as someone, so the test client would set the cookie for you.
self.client.login(username,password)
should do. Refer the documentation for more.
Contrary to the most upvoted answer, you CAN set cookies directly on the test client.
Remember everything is an object, you just have to know where/what to patch
so it goes like this:
client.cookies[key] = data
client.cookies is an instance of http.cookies.SimpleCookie from the standard library and it behaves like a dict. so you can use .update for bulk updates to a cookies value. This can be useful if you want to alter other cookie values like max-age, path domain etc.
Finally, if you want to set a signed_cookie, You can reuse the helpers from django like this:
from django.core.signing import get_cookie_signer
signed_cookie_value = get_cookie_signer(salt=key).sign(data)
client.cookies[key] = signed_cookie_value
Pay attention to the salt. It has to match on both ends (Signing and retrieval). A Different salt value for signing would generate a different cookie that cannot be retrieved when you call response.get_signed_cookie(key)
For other people who are running into this problem please be aware that the Client.logout() function will throw away your cookies. For example:
response = self.client.post(self.url, self.data)
print response.client.cookies.items() # Displays the cookie you just set
self.client.logout()
response = self.client.post(reverse('loginpage'), {'username': 'username', 'password': 'password'}, follow=True)
print response.client.cookies.items() # Does not display the cookie you set before since it got destroyed by logout()
To make sure your cookies stay alive during testing make a call to your logout page in stead of using the Client.logout() function, like so:
response = self.client.post(self.url, self.data)
print response.client.cookies.items() # Displays the cookie you just set
self.client.get(reverse('logoutpage'))
response = self.client.post(reverse('loginpage'), {'username': 'username', 'password': 'password'}, follow=True)
print response.client.cookies.items() # Does display the cookie you set before since it did not get destroyed by client.logout()