[Zope-dev] [patch] More secure cookie crumbler?
Chris Withers
chris at simplistix.co.uk
Mon Apr 12 07:32:24 EDT 2004
Hi Shane and zope-dev,
I think the attached patch (against CookieCrumbler 1.1) makes CookieCrumbler a
little more secure.
It makes CookieCrumbler not store the user's password and username on the
browser side and rotates the token stored on the browser side ever 10 seconds or
time between request, whichever is longer. It also provides a session timeout
controlled by the server.
I thought I'd post it here so people could have a look and let me know what you
think.
If the patch is sound, it'd be great to see the patch make it into the next
release of CookieCrumbler, and maybe the CMF and Plone HEADs?
cheers,
Chris
PS: To make cookie auth properly secure, you really need to be working over SSL
only, and in addition, you should tweak CookieCrumbler further so that it sets
the secure session bit, meaning your sessions should only get returned over a
secure connection... mindyou, to get basic auth to be even vaguely secure, you
also need to be working over SSL ;-) (and you should never let your users change
their password without some other form of authentication, or at the very least
making them enter their old password in the same atomic operation as setting
their new password ;-)
--
Simplistix - Content Management, Zope & Python Consulting
- http://www.simplistix.co.uk
-------------- next part --------------
Index: CookieCrumbler.py
===================================================================
--- CookieCrumbler.py (revision 207)
+++ CookieCrumbler.py (working copy)
@@ -24,17 +24,39 @@
from AccessControl import getSecurityManager, ClassSecurityInfo, Permissions
from ZPublisher import BeforeTraverse
import Globals
+import threading
from Globals import HTMLFile
from zLOG import LOG, ERROR
from ZPublisher.HTTPRequest import HTTPRequest
from OFS.Folder import Folder
+from BTrees.OOBTree import OOBTree
+from time import time
-
# Constants.
ATTEMPT_NONE = 0 # No attempt at authentication
ATTEMPT_LOGIN = 1 # Attempt to log in
ATTEMPT_RESUME = 2 # Attempt to resume session
+# set this to 0 to get "old style" sessioning
+more_secure = 1
+
+# the default store of session information, for "more secure" sessioning
+lock = threading.Lock()
+session2ac = OOBTree()
+session2reset = OOBTree()
+time2session = OOBTree()
+import random
+try:
+ jn = random.randint(0,2L**160)
+ def getRandom():
+ return hex(random.randint(0,2**160))[2:-1]
+except:
+ # python < 2.3
+ jn = random.random()
+ def getRandom():
+ return str(random.random())[2:]
+random.jumpahead(jn)
+
ModifyCookieCrumblers = 'Modify Cookie Crumblers'
ViewManagementScreens = Permissions.view_management_screens
@@ -115,9 +137,74 @@
return getattr( self.aq_inner.aq_parent, name, default )
security.declarePrivate('defaultSetAuthCookie')
- def defaultSetAuthCookie( self, resp, cookie_name, cookie_value ):
- resp.setCookie( cookie_name, cookie_value, path=self.getCookiePath())
+ security.declarePrivate('defaultGetAuthCookie')
+ security.declarePrivate('defaultExpireOldSessions')
+ if more_secure:
+ def defaultSetAuthCookie( self, resp, cookie_name, cookie_value ):
+ session = getRandom()
+ global session2ac
+ global session2reset
+ global time2session
+ lock.acquire()
+ session2ac[session]=cookie_value
+ # tweak this for session expirey time
+ t = time()
+ expiretime = t+20*60 # 20 mins
+ if not time2session.has_key(expiretime):
+ time2session[expiretime]=[]
+ time2session[expiretime].append(session)
+ # cookie reset time (this allows rotating session values
+ # while still letting simultaneous requests such as ZMI
+ # keep working)
+ session2reset[session]=t+10 #10 secs
+ lock.release()
+ resp.setCookie( cookie_name, session, path=self.getCookiePath())
+ def defaultGetAuthCookie( self, resp, cookie_name, acc ):
+ lock.acquire()
+ ac = session2ac.get(acc,None)
+ if ac is None:
+ lock.release()
+ method = self.getCookieMethod( 'expireAuthCookie'
+ , self.defaultExpireAuthCookie )
+ method( resp, cookie_name=self.auth_cookie )
+ return None
+ t = session2reset[acc]
+ lock.release()
+ if t<time():
+ del session2ac[acc]
+ del session2reset[acc]
+ method = self.getCookieMethod( 'setAuthCookie'
+ , self.defaultSetAuthCookie )
+ method( resp, cookie_name, ac )
+ return ac
+
+ def defaultExpireOldSessions(self):
+ global session2ac
+ global session2reset
+ global time2session
+ expiretime = time()
+ lock.acquire()
+ for t,sessions in time2session.items(0,expiretime):
+ for session in sessions:
+ try:
+ del session2ac[session]
+ del session2reset[session]
+ except KeyError:
+ # it's already gone
+ pass
+ del time2session[t]
+ lock.release()
+ else:
+ def defaultSetAuthCookie( self, resp, cookie_name, cookie_value ):
+ resp.setCookie( cookie_name, quote(cookie_value), path=self.getCookiePath())
+
+ def defaultGetAuthCookie( self, acc ):
+ return unquote(acc)
+
+ def defaultExpireOldSessions(self):
+ pass
+
security.declarePrivate('defaultExpireAuthCookie')
def defaultExpireAuthCookie( self, resp, cookie_name ):
resp.expireCookie( cookie_name, path=self.getCookiePath())
@@ -167,15 +254,22 @@
path=self.getCookiePath())
method = self.getCookieMethod( 'setAuthCookie'
, self.defaultSetAuthCookie )
- method( resp, self.auth_cookie, quote( ac ) )
+ method( resp, self.auth_cookie, ac )
self.delRequestVar(req, self.name_cookie)
self.delRequestVar(req, self.pw_cookie)
elif req.has_key(self.auth_cookie):
+ # take the opportunity to potentially delete expired sessions
+ method = self.getCookieMethod( 'expireOldSessions'
+ , self.defaultExpireOldSessions )
+ method()
# Attempt to resume a session if the cookie is valid.
# Copy __ac to the auth header.
- ac = unquote(req[self.auth_cookie])
- if ac and ac != 'deleted':
+ acc = req[self.auth_cookie]
+ if acc and acc != 'deleted':
+ method = self.getCookieMethod( 'getAuthCookie'
+ , self.defaultGetAuthCookie )
+ ac = method( resp, self.auth_cookie, acc)
try:
decodestring(ac)
except:
More information about the Zope-Dev
mailing list