[Zope-CMF] CookieCrumbler security issue?
Lennart Regebro
regebro at nuxeo.com
Wed Jan 21 12:49:14 EST 2004
Lennart Regebro wrote:
> It's not secure, but it's an improvement. In fact, it's enough of an
> improvement that I might try implementing it in PluggableUserFolders
> cookie_identification plugin. Hmm...
In fact, it was quite easy.
The attached CookieIdentification.py has enough similarities with
CookieCrumbler for the solution to be transferrable.
I decided to store the mappings between the token (which is just a
number) and the password in a TemporaryFolder. I don't know if that
makes sense. Maybe it's stupid, I don't know.
I also still send the username, I just replace the password. Not that I
know why. It just felt right.
Also, there is a 'ZEO Compatibility' checkbox to revert to the old
behaviour if desired. :)
//Lennart
-------------- next part --------------
# Copyright (c) 2003 Nuxeo SARL <http://nuxeo.com>
# Copyright (c) 2003 CEA <http://www.cea.fr>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
# 02111-1307, USA.
#
# $Id: CookieIdentification.py,v 1.10 2004/01/21 11:48:53 regebro Exp $
__doc__ = '''Apache SSL Identification Plugin'''
__version__ = '$Revision: 1.10 $'[11:-2]
import sys, random
from urllib import quote, unquote
from PluggableUserFolder import LOG, DEBUG, ERROR
from Globals import MessageDialog
from base64 import decodestring, encodestring
from Acquisition import aq_base
from AccessControl import ClassSecurityInfo
from OFS.PropertyManager import PropertyManager
from OFS.SimpleItem import SimpleItem
from ZPublisher.HTTPRequest import HTTPRequest
from Products.TemporaryFolder.TemporaryFolder import MountedTemporaryFolder
from PluginInterfaces import IIdentificationPlugin
class CookieIdentificationPlugin(PropertyManager, SimpleItem):
"""This Basic HTTP Authentication support"""
security = ClassSecurityInfo()
meta_type = 'Cookie Identification'
id = 'cookie_identification'
title = 'Cookie Identification'
__implements__ = (IIdentificationPlugin,)
_properties = ({'id':'auth_cookie', 'type': 'string', 'mode':'w',
'label':'Authentication cookie name'},
{'id':'name_cookie', 'type': 'string', 'mode':'w',
'label':'User name form variable'},
{'id':'pw_cookie', 'type': 'string', 'mode':'w',
'label':'User password form variable'},
{'id': 'zeo_compatibility', 'type': 'boolean', 'mode':'w',
'label': 'ZEO Compatibility'},
)
auth_cookie = '__ac'
name_cookie = '__ac_name'
pw_cookie = '__ac_password'
zeo_compatibility = 0
manage_options = PropertyManager.manage_options + SimpleItem.manage_options
#
# Public API
#
def makeAuthenticationString(self, request, auth):
LOG('CookieIdentification', DEBUG, 'makeAuthenticationString')
if not isinstance(request, HTTPRequest):
LOG('CookieIdentification', DEBUG, 'Not an HTTP Request')
return None
# XXX: what if it is a HEAD ?
if not request['REQUEST_METHOD'] in ('GET', 'PUT', 'POST'):
LOG('CookieIdentification', DEBUG, 'Not a GET, PUT or POST')
return None
# WebDAV isn't supported because there is no way to set the cookies, since
# you need a login web page to set them.
if request.environ.has_key('WEBDAV_SOURCE_PORT'):
LOG('CookieIdentification', DEBUG, 'WebDAV not supported')
return None
if request.has_key(self.pw_cookie) \
and request.has_key(self.name_cookie):
# Attempt to log in and set cookies.
LOG('CookieIdentification', DEBUG, 'Login attempt')
name = request[self.name_cookie]
pw = request[self.pw_cookie]
if not self.zeo_compatibility:
# Store the password in a temporary storage with a ticket
# Send a ticket instead of the password in the cookie.
if not hasattr(self, '__tickets'):
self.__tickets = MountedTemporaryFolder('__tickets')
ticket = str(random.randint(100000000, 999999999 ))
if hasattr(self.__tickets, name):
delattr(self.__tickets, name)
setattr(self.__tickets, name, '%s:%s' % (ticket, pw))
pw = ticket
ac = encodestring('%s:%s' % (name, pw))
response = request['RESPONSE']
LOG('CookieIdentification', DEBUG, 'New cookie login', ac+'\n')
response.setCookie(self.auth_cookie, quote(ac) , path='/')
self.delRequestVar(request, self.name_cookie)
self.delRequestVar(request, self.pw_cookie)
return 'CookieAuth %s' % ac
if request.has_key(self.auth_cookie):
ac = unquote(request[self.auth_cookie])
self.delRequestVar(request, self.auth_cookie)
LOG('CookieIdentification', DEBUG, 'Found cookie login', ac+'\n')
return 'CookieAuth %s' % ac
return None
def canIdentify(self, auth):
if auth and auth.lower().startswith('cookieauth '):
LOG('CookieIdentification', DEBUG, 'CAN identify', auth + '\n')
return 1
LOG('CookieIdentification', DEBUG, 'Can NOT identify', str(auth) + '\n')
return 0
def identify(self, auth):
try:
name, password = tuple(decodestring(
auth.split(' ')[-1]).split(':', 1))
if not self.zeo_compatibility:
ticket = getattr(self.__tickets, name, None)
if not ticket:
# The user has not logged in but sends a ticket anyway.
# Probably the server restarted. Return a bogus password
# to cause authentication errors and new login.
return name, '{SHA}boguspassword'
token, pw = ticket.split(':', 1)
if not password == token:
raise 'Bad Request', 'Invalid authentication token'
# Ok, we got the correct number, it is genuine!
password = pw
except:
raise 'Bad Request', 'Invalid authentication token'
LOG('CookieIdentification', DEBUG, 'Identify',
'User: %s\n' % name)
return name, password
security.declarePublic('logout')
def logout(self):
'''Log out the user and redirect to the logout page.'''
req = self.REQUEST
resp = req['RESPONSE']
resp.expireCookie(self.auth_cookie, path='/')
# XXX No redirect is done. Check with CookieCrimbler
#
# Internal methods
#
security.declarePublic('propertyLabel')
def propertyLabel(self, id):
"""Return a label for the given property id."""
for p in self._properties:
if p['id'] == id:
return p.get('label', id)
return id
security.declarePrivate('delRequestVar')
def delRequestVar(self, req, name):
# No errors of any sort may propagate, and we don't care *what*
# they are, even to log them.
try: del req.other[name]
except: pass
try: del req.form[name]
except: pass
try: del req.cookies[name]
except: pass
try: del req.environ[name]
except: pass
def manage_addCookieIdentificationPlugin(self, REQUEST=None):
""" """
ob = CookieIdentificationPlugin()
self = self.this()
if hasattr(aq_base(self), ob.id):
return MessageDialog(
title='Item Exists',
message='This object already contains an %s' % id.title,
action='%s/manage_main' % REQUEST['URL1'])
self._setObject(ob.id, ob)
if REQUEST is not None:
REQUEST['RESPONSE'].redirect(self.absolute_url() + '/manage_main')
More information about the Zope-CMF
mailing list