Commits

Robert Brewer  committed d465a35

Fix for #709 (Cherrypy accepts user-supplied session identifiers). Also fixed some problems with regenerated id's.

  • Participants
  • Parent commits 124d864

Comments (0)

Files changed (2)

File cherrypy/lib/sessions.py

     
     __metaclass__ = cherrypy._AttributeDocstrings
     
-    id = None
+    _id = None
+    id_observers = None
+    id_observers__doc = "A list of callbacks to which to pass new id's."
+    
     id__doc = "The current session ID."
+    def _get_id(self):
+        return self._id
+    def _set_id(self, value):
+        self._id = value
+        for o in self.id_observers:
+            o(value)
+    id = property(_get_id, _set_id, doc=id__doc)
     
     timeout = 60
     timeout__doc = "Number of minutes after which to delete session data."
     clean_freq__doc = "The poll rate for expired session cleanup in minutes."
     
     def __init__(self, id=None, **kwargs):
+        self.id_observers = []
         self._data = {}
         
         for k, v in kwargs.iteritems():
             self.regenerate()
         else:
             self.id = id
+            if self._load() is None:
+                # Expired or malicious session. Make a new one.
+                # See http://www.cherrypy.org/ticket/709.
+                self.id = None
+                self.regenerate()
     
     def regenerate(self):
         """Replace the current session (with a new id)."""
         data = self._load()
         # data is either None or a tuple (session_data, expiration_time)
         if data is None or data[1] < datetime.datetime.now():
-            # Expired session: flush session data (but keep the same id)
+            # Expired session: flush session data
             self._data = {}
         else:
             self._data = data[0]
     kwargs['timeout'] = timeout
     kwargs['clean_freq'] = clean_freq
     cherrypy.serving.session = sess = storage_class(id, **kwargs)
+    def update_cookie(id):
+        """Update the cookie every time the session id changes."""
+        cherrypy.response.cookie[name] = id
+    sess.id_observers.append(update_cookie)
     
     # Create cherrypy.session which will proxy to cherrypy.serving.session
     if not hasattr(cherrypy, "session"):

File cherrypy/test/test_session.py

         _cp_config = {'tools.sessions.on': True,
                       'tools.sessions.storage_type' : 'ram',
                       'tools.sessions.storage_path' : localDir,
-                      'tools.sessions.timeout': 0.017,    # 1.02 secs
-                      'tools.sessions.clean_freq': 0.017,
+                      'tools.sessions.timeout': (1.0 / 60),
+                      'tools.sessions.clean_freq': (1.0 / 60),
                       }
         
         def testGen(self):
 
 class SessionTest(helper.CPWebCase):
     
+    def tearDown(self):
+        # Clean up sessions.
+        for fname in os.listdir(localDir):
+            if fname.startswith(sessions.FileSession.SESSION_PREFIX):
+                os.unlink(os.path.join(localDir, fname))
+    
     def test_0_Session(self):
         self.getPage('/testStr')
         self.assertBody('1')
         self.getPage('/delkey?key=counter', self.cookies)
         self.assertStatus(200)
         
-        # Wait for the session.timeout (1.02 secs)
-        time.sleep(1.25)
+        # Wait for the session.timeout (1 second)
+        time.sleep(2)
         self.getPage('/')
         self.assertBody('1')
         
         self.assertBody('logged in')
         id2 = self.cookies[0][1].split(";", 1)[0].split("=", 1)[1]
         self.assertNotEqual(id1, id2)
+        
+        self.getPage('/testStr')
+        # grab the cookie ID
+        id1 = self.cookies[0][1].split(";", 1)[0].split("=", 1)[1]
+        self.getPage('/testStr',
+                     headers=[('Cookie',
+                               'session_id=maliciousid; '
+                               'expires=Sat, 27 Oct 2017 04:18:28 GMT; Path=/;')])
+        id2 = self.cookies[0][1].split(";", 1)[0].split("=", 1)[1]
+        self.assertNotEqual(id1, id2)
+        self.assertNotEqual(id2, 'maliciousid')
 
 
 import socket
             self.getPage('/delkey?key=counter', self.cookies)
             self.assertStatus(200)
             
-            # Wait for the session.timeout (1.02 secs)
+            # Wait for the session.timeout (1 second)
             time.sleep(1.25)
             self.getPage('/')
             self.assertBody('1')