session ID collision (security issue) & cookies (Expires vs. Max-Age)

96 views
Skip to first unread message

Jeff

unread,
Feb 3, 2006, 5:10:54 PM2/3/06
to cherrypy-users
Hi, I've been looking into some strange cookie/session issues on
http://diggdot.us/.

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

thakadu

unread,
Feb 3, 2006, 11:31:28 PM2/3/06
to cherrypy-users
I also ran into this problem with IE. I solved it a different way,
simply by setting the cookie in javascript and not via the CherryPy
API. I havent checked with the new IE 7 beta 2 but the problem also
exists for IE 7 beta 1.

Istvan Albert

unread,
Feb 7, 2006, 3:00:01 PM2/7/06
to cherrypy-users
RFC or not many browsers (Internet Explorer and Safari at least) do not
properly recognize the Max-Age directive in the cookies . Cherrypy is
also using this and as a result the browsers will delete the cookies
set like that when the browser is closed. For sessions this is not
really a problem since (IMO) a session should get deleted when a
browser is closed ... in many ways that is the definition of a session
right? Plus it rarely causes problems in real life.

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...).

Jeff

unread,
Feb 7, 2006, 3:23:52 PM2/7/06
to cherrypy-users
> For sessions this is not really a problem since (IMO) a session
> should get deleted when a browser is closed ... in many ways
> that is the definition of a session right? Plus it rarely causes
> problems in real life.

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

Istvan Albert

unread,
Feb 7, 2006, 4:52:20 PM2/7/06
to cherrypy-users
what I was saying is that if you want to remeber users from one day to
another, through browser openings and closings and server restarts etc
the sensible thing to do is to explicitely set a cookie with the user
id or whatever then based on this cookie you can then generate an up to
date session. That's what most sites do. No large site, especially not
slashdot will actually save the objects in your session for weeks that
is just waste (and that's what not expiring a session means).

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.

re...@cherrypy.org

unread,
Feb 8, 2006, 3:22:49 AM2/8/06
to cherrypy-users

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.

Sylvain Hellegouarch

unread,
Feb 8, 2006, 4:35:19 AM2/8/06
to cherryp...@googlegroups.com

> 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.

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

Istvan Albert

unread,
Feb 8, 2006, 8:52:38 AM2/8/06
to cherrypy-users
we generate unique ids with:

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)

Jeff

unread,
Feb 9, 2006, 12:30:18 AM2/9/06
to cherrypy-users
Remi, thanks so much for checking in that fix.

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

thakadu

unread,
Feb 9, 2006, 4:31:38 PM2/9/06
to cherrypy-users
Thats exactly what I do with muti (http://muti.co.za). If you login and
you select the "Autologin" checkbox, then a second cookie is generated,
via javascript since I did this before the cherrypy fix. That second
cookie is saved in a database against the users name. If they close
down the browser and come back the next day the home page will look for
the 40 character random string saved for those users who selected
"autologin" and then generate a "session" cookie through the CherryPy
api just for that session. (A "session" cookie is never persisted to
disk at the client side.)
I am pretty sure that no collisions will happen in my lifetime :)
Slashdot and many other sites use a shorter random string than I do and
I figure if they are confident with it then so am I ;)
Note that my "autologinid" session uses alphabetic characters so I
beliebe the number of unique strings is something like 26**40, probably
close to the number of the electrons in the entire universe or
something like that!

Reply all
Reply to author
Forward
0 new messages