changeset 5469:cce24183de9e

improved ticketing code security support ticket creation for other pages than the current one (needed if POST happens to other page). use hmac/sha1 rather than just sha1 to improve security. add session id and user id to hmac computation input data, so you can't create usable tickets for other (logged in) users or users having a anon session. side effect: if you lose the session (log out, time out), you lose the ticket. if you change your sid or uid (log in), you lose the ticket. remove data from the ticket's "cleartext" part that are not needed there.
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Sun, 31 Jan 2010 22:50:49 +0100
parents be7c57d8e2a3
children 8186aa2c7c9f
files MoinMoin/wikiutil.py
diffstat 1 files changed, 41 insertions(+), 20 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/wikiutil.py	Fri Jan 29 21:12:08 2010 +0100
+++ b/MoinMoin/wikiutil.py	Sun Jan 31 22:50:49 2010 +0100
@@ -2459,10 +2459,12 @@
         return pagename, ""
 
 ########################################################################
-### Tickets - used by RenamePage and DeletePage
+### Tickets - usually used in forms to make sure that form submissions
+### are in response to a form the same user got from moin for the same
+### action and same page.
 ########################################################################
 
-def createTicket(request, tm=None, action=None):
+def createTicket(request, tm=None, action=None, pagename=None):
     """ Create a ticket using a configured secret
 
         @param tm: unix timestamp (optional, uses current time if not given)
@@ -2470,31 +2472,48 @@
                        Note: if you create a ticket for a form that calls another
                              action than the current one, you MUST specify the
                              action you call when posting the form.
+        @param pagename: page name (optional, uses current page name if not given)
+                       Note: if you create a ticket for a form that posts to another
+                             page than the current one, you MUST specify the
+                             page name you use when posting the form.
     """
 
-    from MoinMoin.support.python_compatibility import hash_new
+    from MoinMoin.support.python_compatibility import hmac_new
     if tm is None:
+        # for age-check of ticket
         tm = "%010x" % time.time()
 
-    # make the ticket specific to the page and action:
-    try:
-        pagename = quoteWikinameURL(request.page.page_name)
-    except:
-        pagename = 'None'
+    # make the ticket very specific:
+    if pagename is None:
+        try:
+            pagename = request.page.page_name
+        except:
+            pagename = ''
 
     if action is None:
-        try:
-            action = request.action
-        except:
-            action = 'None'
-
-    secret = request.cfg.secrets['wikiutil/tickets']
-    digest = hash_new('sha1', secret)
-
-    ticket = "%s.%s.%s" % (tm, pagename, action)
-    digest.update(ticket)
-
-    return "%s.%s" % (ticket, digest.hexdigest())
+        action = request.action
+
+    if request.session:
+        # either a user is logged in or we have a anon session -
+        # if session times out, ticket will get invalid
+        sid = request.session.sid
+    else:
+        sid = ''
+
+    if request.user.valid:
+        uid = request.user.id
+    else:
+        uid = ''
+
+    hmac_data = []
+    for value in [tm, pagename, action, sid, uid, ]:
+        if isinstance(value, unicode):
+            value = value.encode('utf-8')
+        hmac_data.append(value)
+
+    hmac = hmac_new(request.cfg.secrets['wikiutil/tickets'],
+                    ''.join(hmac_data))
+    return "%s.%s" % (tm, hmac.hexdigest())
 
 
 def checkTicket(request, ticket):
@@ -2511,6 +2530,8 @@
         # we don't accept tickets older than 10h
         logging.debug("checkTicket: too old ticket, timestamp %r" % timestamp)
         return False
+    # Note: if the session timed out, that will also invalidate the ticket,
+    #       if the ticket was created within a session.
     ourticket = createTicket(request, timestamp_str)
     logging.debug("checkTicket: returning %r, got %r, expected %r" % (ticket == ourticket, ticket, ourticket))
     return ticket == ourticket