I found a couple of serious issues.
=== Max-Age vs. Expires ===
A CherryPy-based app sends the following HTTP response header:
Set-Cookie: sessionID=1463e23a0b32c0a036b10a83ded76f9245b4a3b4;
Max-Age=31104000; Path=/; Version=1;
This cookie does not write out a cookie to disk on Internet Explorer
6.0 (and likely earlier versions). Internet Explorer will pass the
cookie back and forth to the server will the browser is still open.
When you close and re-open the browser, the session is lost. The
cookie was never written to disk.
A "correct" (I'll explain the quotes in a minute) header - for example
the HTTP response header returned by www.cherrypy.org - seems to be:
Set-Cookie: trac_session=c5429b9a8a69401200446c23; expires=Mon,
27-May-2019 23:53:14 GMT;
Note the "expires" vs "Max-Age".
This header will write the cookie to Internet Explorer's Cookies
folder. This cookie is preserved between closing/opening your browser.
I put "correct" in quotes because according to RFC 2109
(http://www.faqs.org/rfcs/rfc2109.html), Max-Age should be used in
cookies, not the older Expires. However, only Expires appears to work
on Internet Explorer.
I'm really surprised this hasn't come up before...I have a hard time
believing I'm the only one trying to support Internet Explorer sessions
that last longer than a single browser window being open.
Here's my quick fix in sessionfilter.py:
#cookie[cookieName]['max-age'] = sess.sessionTimeout * 60
t = datetime.timedelta(seconds = sess.sessionTimeout * 60)
expirationTime = datetime.datetime.now() + t
cookie[cookieName]['expires'] = expirationTime.strftime("%a,
%d-%b-%Y %H:%M:%S GMT")
(this isn't totally correct - this is setting a local time, not the GMT
time...)
=== Session ID collision ===
Digging through my debug logs I noticed session ID collisions. It
looks like a ticket is sitting at the bottom of the queue about this
(http://www.cherrypy.org/ticket/408).
I'm really surprised to see collisions since it should theoritically be
very rare. But my log shows 113 instances of newly created session IDs
that already exist in my database, causing two users to end up with the
same session ID. On diggdot.us it's not a tragic security flaw since
I'm not storing any serious data per user, but on some other sites this
could expose some potentially private data to the wrong user. Seems
like a major security issue.
Note that I'm using a very long expiration time, so my sessions table
is growing quite large. But it's still surprising to see that many
collisions. For a fix, the generateSessionID() function could check to
see if the new session ID already exists and try creating a new ID if
it does.
I'm using CP 2.1.1, but I see these issues still exist in CP 2.2.0beta.
Thanks.
Jeff Marshall
mars...@frozenbear.com
If one is setting their own cookies however they must use the expires
header as you do (which is also a lot messier to generate...).
I disagree. The sessionfilter will not match many people's
expectations of what setting the max-age on the session cookie will do
for you.
Say you want to write a site like slashdot where it remembers you when
you come back the next day after closing/opening your browser, are you
suggesting we cannot use the sessionfilter module? The point of the
sessionfilter is that it providers the handy cherrypy.session['userid']
type of access, and the tracking of the sessions in a database. If I
can't use sessionfilter to define my user's login to last for 30 days
or so, then in my app I going to rebuild all the functionality of the
sessionfilter module, the sessions table and the the cherrypy.session
object simply so that my cookie can say "expires" instead of "max-age"?
I think that since sessionfilter advertises that it uses a cookie to
store the session, then it should properly write out a cookie on all
browsers - especially the most common browser. Otherwise the time you
set in the max-age has no meaning.
Jeff
Now you could use the session variable as a free vehicle and as a
replacement for this user identification. Alas that will not work with
the current cherrypy.
All I'm saying is that in the end you're might be better off doing it
properly rather than trying to solve a problem (rembering users for
long term) with a solution that was never meant to work that way
(storing data for short term) because if you choose the latter you'll
soon find yourself needing all kinds of other "features" that should
not be part of the framework rather then your own application.
just an opinion
i.
I've updated the session_filter to use "expires" instead of "max-age"
(changeset [963])
> === Session ID collision ===
>
> Digging through my debug logs I noticed session ID collisions. It
> looks like a ticket is sitting at the bottom of the queue about this
> (http://www.cherrypy.org/ticket/408).
>
> I'm really surprised to see collisions since it should theoritically be
> very rare. But my log shows 113 instances of newly created session IDs
> that already exist in my database, causing two users to end up with the
> same session ID. On diggdot.us it's not a tragic security flaw since
> I'm not storing any serious data per user, but on some other sites this
> could expose some potentially private data to the wrong user. Seems
> like a major security issue.
>
> Note that I'm using a very long expiration time, so my sessions table
> is growing quite large. But it's still surprising to see that many
> collisions. For a fix, the generateSessionID() function could check to
> see if the new session ID already exists and try creating a new ID if
> it does.
>
> I'm using CP 2.1.1, but I see these issues still exist in CP 2.2.0beta.
Well, the default session id generator uses "sha.new('%s' %
random.random()).hexdigest()" to generate the session id. This
generates a "fairly random" 40 character string.
I'm not an expert on statistics but I would expect collisions to be
likely to happen only on a very, very large set of visitors ... 113
collisions seems very unlikely ... How many session ids do you have ?
Are you sure it's not another problem (like the same session id from
the same user somehow appearing twice in your DB ...) ?
A few more comments:
- In the current trunk you can specify your own session id generation
function by passing it via "session_filter.generate_session_id".
- If someone wants to suggest a better session id generator than the
one we currently use (provided it's fast) I'm not against changing the
current one.
- Unfortunately, checking that the generated session id doesn't already
exist can be very expensive if there are lots of sessions (especially
for the file backend) so this approach seems unpractical ...
Remi.
Not so sure.
random.random() has a large period but it seems there could be a problem
in a multi-threaded environment as the python doc suggests:
"The functions supplied by this module are actually bound methods of a
hidden instance of the random.Random class. You can instantiate your own
instances of Random to get generators that don't share state. This is
especially useful for multi-threaded programs, creating a different
instance of Random for each thread, and using the jumpahead() method to
ensure that the generated sequences seen by each thread don't overlap."
Remi, is this what you do?
- Sylvain
random.getrandbits( size )
where size is a integer of the desired lenght. Setting that to say 128
makes the chances of collision practically nil.
(we also compute a hexdigest of it to make it a bit shorter)
On the session ID collisions, I'm going to assume I have a bug in my
code at this point. The collisions just seem too hard to statistically
believe for me, too. I'm using my own MySQLStorage code for the
sessions and that might have a problem. At a minimum, I'll hook up
some more debug code to find out what is happening when I see the
collision.
The configurable generate_session_id function is a nice touch.
Thanks.
Jeff