[Checkins] SVN: z3c.authenticator/trunk/ Security Fix: move camefrom url to a session variable instead of exposing
Roger Ineichen
roger at projekt01.ch
Sun Oct 18 00:41:54 EDT 2009
Log message for revision 105125:
Security Fix: move camefrom url to a session variable instead of exposing
the url in the login form. Because the camefrom url is built at server side
based on local information and will always only use internal traversal names.
Exposing this camefrom query in the login url gives others only a point to
attack because it could be simply set by a unfriendly domain with a custom
url. This is much better since such a unfriendly 3rd party domain url doesn't
get redirected by default based on the changes in zope.publisher's redirect
method. (zope.publisher 3.9.3 does only redirect to urls located in the same
domain by default)
Remove all camefrom widgets and queries in our custom forms if you use any.
You can just set and get the camefrom session variable in your custom forms
if you need to.
Changed:
U z3c.authenticator/trunk/CHANGES.txt
U z3c.authenticator/trunk/setup.py
U z3c.authenticator/trunk/src/z3c/authenticator/browser/login.py
U z3c.authenticator/trunk/src/z3c/authenticator/credential.py
U z3c.authenticator/trunk/src/z3c/authenticator/interfaces.py
-=-
Modified: z3c.authenticator/trunk/CHANGES.txt
===================================================================
--- z3c.authenticator/trunk/CHANGES.txt 2009-10-18 03:51:48 UTC (rev 105124)
+++ z3c.authenticator/trunk/CHANGES.txt 2009-10-18 04:41:53 UTC (rev 105125)
@@ -2,10 +2,22 @@
CHANGES
=======
-0.7.2 (unreleased)
+0.8.0 (unreleased)
------------------
-- ...
+- Security Fix: move camefrom url to a session variable instead of exposing
+ the url in the login form. Because the camefrom url is built at server side
+ based on local information and will always only use internal traversal names.
+ Exposing this camefrom query in the login url gives others only a point to
+ attack because it could be simply set by a unfriendly domain with a custom
+ url. This is much better since such a unfriendly 3rd party domain url doesn't
+ get redirected by default based on the changes in zope.publisher's redirect
+ method. (zope.publisher 3.9.3 does only redirect to urls located in the same
+ domain by default)
+
+ Remove all camefrom widgets and queries in our custom forms if you use any.
+ You can just set and get the camefrom session variable in you rcustom forms
+ if you need to.
0.7.1 (2009-08-19)
Modified: z3c.authenticator/trunk/setup.py
===================================================================
--- z3c.authenticator/trunk/setup.py 2009-10-18 03:51:48 UTC (rev 105124)
+++ z3c.authenticator/trunk/setup.py 2009-10-18 04:41:53 UTC (rev 105125)
@@ -23,7 +23,7 @@
setup (
name='z3c.authenticator',
- version='0.7.2dev',
+ version='0.8.0dev',
author = "Roger Ineichen and the Zope Community",
author_email = "zope-dev at zope.org",
description = "IAuthentication implementation for for Zope3",
Modified: z3c.authenticator/trunk/src/z3c/authenticator/browser/login.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/browser/login.py 2009-10-18 03:51:48 UTC (rev 105124)
+++ z3c.authenticator/trunk/src/z3c/authenticator/browser/login.py 2009-10-18 04:41:53 UTC (rev 105125)
@@ -10,12 +10,13 @@
__docformat__ = "reStructuredText"
import zope.component
+from zope.authentication.interfaces import IAuthentication
+from zope.authentication.interfaces import ILogout
+from zope.authentication.interfaces import IUnauthenticatedPrincipal
from zope.publisher.browser import BrowserPage
+from zope.session.interfaces import ISession
+from zope.site import hooks
from zope.traversing.browser import absoluteURL
-from zope.site import hooks
-from zope.authentication.interfaces import IUnauthenticatedPrincipal
-from zope.authentication.interfaces import IAuthentication
-from zope.authentication.interfaces import ILogout
from z3c.form.interfaces import HIDDEN_MODE
from z3c.form.interfaces import IWidgets
@@ -30,7 +31,9 @@
class LoginForm(form.Form):
- """Login form."""
+ """Login form without prefix in form and widget which works with session
+ credentail plugin out of the box.
+ """
template = getPageTemplate()
layout = getLayoutTemplate()
@@ -39,39 +42,36 @@
nextURL = None
prefix = ''
- def getCameFrom(self):
- camefrom = self.request.get('camefrom', None)
- if camefrom is None:
- site = hooks.getSite()
- camefrom = '%s/index.html' % absoluteURL(site, self.request)
- return camefrom
+ @property
+ def message(self):
+ if IUnauthenticatedPrincipal.providedBy(self.request.principal):
+ return _(u'Please provide Login Information')
+ return u''
def updateWidgets(self):
self.widgets = zope.component.getMultiAdapter(
(self, self.request, self.getContent()), IWidgets)
+ # the session credential use input fields without prefixes
self.widgets.prefix = ''
self.widgets.ignoreContext = True
self.widgets.ignoreReadonly = True
self.widgets.update()
- self.widgets['camefrom'].mode = HIDDEN_MODE
- self.widgets['camefrom'].value = self.getCameFrom()
- @property
- def message(self):
- if IUnauthenticatedPrincipal.providedBy(self.request.principal):
- return _(u'Please provide Login Information')
- return u''
-
@button.buttonAndHandler(_('Log-in'))
def handleLogin(self, action):
"""Handle the subscribe action will register and login a user."""
if not IUnauthenticatedPrincipal.providedBy(self.request.principal):
- data, errors = self.widgets.extract()
- self.nextURL = data['camefrom']
+ session = ISession(self.request, None)
+ sessionData = session.get('z3c.authenticator.credential.session')
+ if sessionData is not None and sessionData.get('camefrom'):
+ self.nextURL = sessionData['camefrom']
+ sessionData['camefrom'] = None
def __call__(self):
self.update()
if self.nextURL is not None:
+ # the redirect method will prevent us to redirect to a 3rd party
+ # domains since zope.publisher version 3.9.3
self.request.response.redirect(self.nextURL)
return ""
else:
Modified: z3c.authenticator/trunk/src/z3c/authenticator/credential.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/credential.py 2009-10-18 03:51:48 UTC (rev 105124)
+++ z3c.authenticator/trunk/src/z3c/authenticator/credential.py 2009-10-18 04:41:53 UTC (rev 105125)
@@ -20,6 +20,7 @@
import transaction
import persistent
from urllib import urlencode
+from urllib import quote
import zope.interface
from zope.publisher.interfaces.http import IHTTPRequest
@@ -320,8 +321,16 @@
def challenge(self, request):
"""Challenges by redirecting to a login form.
- To illustrate, we'll create a test request:
+ To illustrate how a session plugin works, we'll first setup some session
+ machinery:
+
+ >>> from zope.app.testing import placelesssetup
+ >>> from z3c.authenticator.testing import sessionSetUp
+ >>> placelesssetup.setUp()
+ >>> sessionSetUp()
+ and we'll create a test request:
+
>>> from zope.publisher.browser import TestRequest
>>> request = TestRequest()
@@ -342,8 +351,16 @@
>>> request.response.getStatus()
302
>>> request.response.getHeader('location')
- 'http://127.0.0.1/@@loginForm.html?camefrom=%2F'
+ 'http://127.0.0.1/@@loginForm.html'
+ and the camefrom session contains the camefrom url which is our
+ application root by default:
+
+ >>> session = ISession(request, None)
+ >>> sessionData = session['z3c.authenticator.credential.session']
+ >>> sessionData['camefrom']
+ '/'
+
The plugin redirects to the page defined by the loginpagename
attribute:
@@ -351,8 +368,16 @@
>>> plugin.challenge(request)
True
>>> request.response.getHeader('location')
- 'http://127.0.0.1/@@mylogin.html?camefrom=%2F'
+ 'http://127.0.0.1/@@mylogin.html'
+ and the camefrom session contains the camefrom url which is our
+ application root by default:
+
+ >>> session = ISession(request, None)
+ >>> sessionData = session['z3c.authenticator.credential.session']
+ >>> sessionData['camefrom']
+ '/'
+
It also provides the request URL as a 'camefrom' GET style parameter.
To illustrate, we'll pretend we've traversed a couple names:
@@ -371,11 +396,18 @@
>>> plugin.challenge(request)
True
- We see the 'camefrom' points to the requested URL:
+ We see the url points to the login form URL:
>>> request.response.getHeader('location') # doctest: +ELLIPSIS
- '.../@@mylogin.html?camefrom=%2Ffoo%2Fbar%2Ffolder%2Fpage+1.html%3Fq%3Dvalue'
+ 'http://127.0.0.1/@@mylogin.html'
+ and the camefrom session contains the camefrom url:
+
+ >>> session = ISession(request, None)
+ >>> sessionData = session['z3c.authenticator.credential.session']
+ >>> sessionData['camefrom']
+ u'/foo/bar/folder/page%201.html?q=value'
+
If the given challenge argument doesn't provide IHTTPRequest the
result will always be False:
@@ -398,10 +430,14 @@
camefrom = '/'.join([request.getURL(path_only=True)] + stack)
if query:
camefrom = camefrom + '?' + query
- url = '%s/@@%s?%s' % (absoluteURL(site, request),
- self.loginpagename,
- urlencode({'camefrom': camefrom}))
+ url = '%s/@@%s' % (absoluteURL(site, request), self.loginpagename)
+ # only redirect to the login form
request.response.redirect(url)
+ # and store the camefrom url into a session variable, then this url
+ # should not get exposed in the login form url.
+ session = ISession(request, None)
+ sessionData = session['z3c.authenticator.credential.session']
+ sessionData['camefrom'] = camefrom.replace(' ', '%20')
return True
def logout(self, request):
@@ -411,5 +447,6 @@
sessionData = ISession(request)['z3c.authenticator.credential.session']
sessionData['credentials'] = None
+ sessionData['camefrom'] = None
transaction.commit()
return True
Modified: z3c.authenticator/trunk/src/z3c/authenticator/interfaces.py
===================================================================
--- z3c.authenticator/trunk/src/z3c/authenticator/interfaces.py 2009-10-18 03:51:48 UTC (rev 105124)
+++ z3c.authenticator/trunk/src/z3c/authenticator/interfaces.py 2009-10-18 04:41:53 UTC (rev 105125)
@@ -534,11 +534,7 @@
title=_(u'Password'),
description=_(u'Your password.'))
- camefrom = zope.schema.TextLine(
- title=_(u'Camefrom'),
- description=_(u'The url which the user came form.'))
-
# queriable search interfaces
class IQueriableAuthenticator(ISearchable):
"""Indicates the authenticator provides a search UI for principals."""
More information about the checkins
mailing list