[Zope-Checkins] SVN: Zope/hotfixes/ Import POST-only hotfix

Martijn Pieters mj at zopatista.com
Tue Mar 20 05:09:04 EDT 2007


Log message for revision 73391:
  Import POST-only hotfix

Changed:
  A   Zope/hotfixes/README.txt
  A   Zope/hotfixes/__init__.py
  A   Zope/hotfixes/tests/
  A   Zope/hotfixes/tests/__init__.py
  A   Zope/hotfixes/tests/test_hotfix.py
  A   Zope/hotfixes/version.txt

-=-
Added: Zope/hotfixes/README.txt
===================================================================
--- Zope/hotfixes/README.txt	2007-03-20 09:05:56 UTC (rev 73390)
+++ Zope/hotfixes/README.txt	2007-03-20 09:09:02 UTC (rev 73391)
@@ -0,0 +1,62 @@
+Hotfix-20070320 README
+
+    This hotfix corrects a cross-site scripting vulnerability in Zope2,
+    where an attacker can use a hidden GET request to leverage a 
+    authenticated user's credentials to alter security settings and/or
+    user accounts.
+
+    Note that this fix only protects against GET requests, any site that
+    allows endusers to create auto-submitting forms (through javascript)
+    will remain vulnerable.
+    
+    The hotfix may be removed after upgrading to a version of Zope2 more
+    recent than this hotfix.
+
+  Affected Versions
+
+    - Zope 2.8.0 - 2.8.8
+
+    - Zope 2.9.0 - 2.9.6
+
+    - Zope 2.10.0 - 2.10.2
+    
+    - Earlier versions of Zope 2 are affected as well, but no new
+      releases for older major Zope releases (Zope 2.7 and earlier) will
+      be made. This Hotfix may work for older versions, but this has not
+      been tested.
+    
+  Installing the Hotfix
+
+    This hotfix is installed as a standard Zope2 product.  The following
+    examples assume that your Zope instance is located at
+    '/var/zope/instance':  please adjust according to your actual
+    instance path.  Also note that hotfix products are *not* intended
+    for installation into the "software home" of your Zope.
+
+      1. Unpack the tarball / zipfile for the Hotfix into a temporary
+         location::
+
+          $ cd /tmp
+          $ tar xzf ~/Hotfix_20070320.tar.gz
+
+      2. Copy or move the product directory from the unpacked directory
+         to the 'Products' directory of your Zope instance::
+
+          $ cp -a /tmp/Hotfix_20070320/ /var/zope/instance/Products/
+
+      3. Restart Zope::
+
+          $ /var/zope/instance/bin/zopectl restart
+
+  Uninstalling the Hotfix
+
+    After upgrading Zope to one of the fixed versions, you should remove
+    this hotfix product from your Zope instance.
+
+      1. Remove the product directory from your instance 'Products'::
+
+          $ rm -rf /var/zope/instance/Products/Hotfix_20070320/
+
+      2. Restart Zope::
+
+          $ /var/zope/instance/bin/zopectl restart

Added: Zope/hotfixes/__init__.py
===================================================================
--- Zope/hotfixes/__init__.py	2007-03-20 09:05:56 UTC (rev 73390)
+++ Zope/hotfixes/__init__.py	2007-03-20 09:09:02 UTC (rev 73391)
@@ -0,0 +1,122 @@
+#############################################################################
+#
+# Copyright (c) 2007 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+
+"""Hotfix_20070319
+
+Protect security methods against GET requests.
+
+"""
+
+import inspect
+from zExceptions import Forbidden
+from ZPublisher.HTTPRequest import HTTPRequest
+
+def _buildFacade(spec, docstring):
+    """Build a facade function, matching the decorated method in signature.
+    
+    Note that defaults are replaced by None, and _curried will reconstruct
+    these to preserve mutable defaults.
+    
+    """
+    args = inspect.formatargspec(formatvalue=lambda v: '=None', *spec)
+    callargs = inspect.formatargspec(formatvalue=lambda v: '', *spec)
+    return 'def _facade%s:\n    """%s"""\n    return _curried%s' % (
+        args, docstring, callargs)
+
+def postonly(callable):
+    """Only allow callable when request method is POST."""
+    spec = inspect.getargspec(callable)
+    args, defaults = spec[0], spec[3]
+    try:
+        r_index = args.index('REQUEST')
+    except ValueError:
+        raise ValueError('No REQUEST parameter in callable signature')
+    
+    arglen = len(args)
+    if defaults is not None:
+        defaults = zip(args[arglen - len(defaults):], defaults)
+        arglen -= len(defaults)
+            
+    def _curried(*args, **kw):
+        request = None
+        if len(args) > r_index:
+            request = args[r_index]
+            
+        if isinstance(request, HTTPRequest):
+            if request.get('REQUEST_METHOD', 'GET').upper() != 'POST':
+                raise Forbidden('Request must be POST')
+
+        # Reconstruct keyword arguments
+        if defaults is not None:
+            args, kwparams = args[:arglen], args[arglen:]
+            for positional, (key, default) in zip(kwparams, defaults):
+                if positional is None:
+                    kw[key] = default
+                else:
+                    kw[key] = positional
+                    
+        return callable(*args, **kw)
+    
+    facade_globs = dict(_curried=_curried)
+    exec _buildFacade(spec, callable.__doc__) in facade_globs
+    return facade_globs['_facade']
+
+# Add REQUEST to BasicUserFolder.userFolder* methods as well as protect them
+from AccessControl.User import BasicUserFolder
+
+_original_ufAddUser = BasicUserFolder.userFolderAddUser
+def ufAddUser(self, name, password, roles, domains, REQUEST=None, **kw):
+    return _original_ufAddUser(self, name, password, roles, domains, **kw)
+ufAddUser.__doc__ = _original_ufAddUser.__doc__
+BasicUserFolder.userFolderAddUser = postonly(ufAddUser)
+
+_original_ufEditUser = BasicUserFolder.userFolderEditUser
+def ufEditUser(self, name, password, roles, domains, REQUEST=None, **kw):
+    return _original_ufEditUser(self, name, password, roles, domains, **kw)
+ufEditUser.__doc__ = _original_ufEditUser.__doc__
+BasicUserFolder.userFolderEditUser = postonly(ufEditUser)
+
+_original_ufDelUsers = BasicUserFolder.userFolderDelUsers
+def ufDelUsers(self, names, REQUEST=None):
+    return _original_ufDelUsers(self, names)
+ufDelUsers.__doc__ = _original_ufDelUsers.__doc__
+BasicUserFolder.userFolderDelUsers = postonly(ufDelUsers)
+
+BasicUserFolder.manage_setUserFolderProperties = postonly(
+    BasicUserFolder.manage_setUserFolderProperties)
+BasicUserFolder._addUser = postonly(BasicUserFolder._addUser)
+BasicUserFolder._changeUser = postonly(BasicUserFolder._changeUser)
+BasicUserFolder._delUsers = postonly(BasicUserFolder._delUsers)
+
+from AccessControl.Owned import Owned
+Owned.manage_takeOwnership = postonly(Owned.manage_takeOwnership)
+Owned.manage_changeOwnershipType = postonly(Owned.manage_changeOwnershipType)
+
+from AccessControl.PermissionMapping import RoleManager as PMRM
+PMRM.manage_setPermissionMapping = postonly(PMRM.manage_setPermissionMapping)
+
+from AccessControl.Role import RoleManager as RMRM
+RMRM.manage_acquiredPermissions = postonly(RMRM.manage_acquiredPermissions)
+RMRM.manage_permission = postonly(RMRM.manage_permission)
+RMRM.manage_changePermissions = postonly(RMRM.manage_changePermissions)
+RMRM.manage_addLocalRoles = postonly(RMRM.manage_addLocalRoles)
+RMRM.manage_setLocalRoles = postonly(RMRM.manage_setLocalRoles)
+RMRM.manage_delLocalRoles = postonly(RMRM.manage_delLocalRoles)
+RMRM._addRole = postonly(RMRM._addRole)
+RMRM._delRoles = postonly(RMRM._delRoles)
+
+from OFS.DTMLMethod import DTMLMethod
+DTMLMethod.manage_proxy = postonly(DTMLMethod.manage_proxy)
+
+from Products.PythonScripts.PythonScript import PythonScript
+PythonScript.manage_proxy = postonly(PythonScript.manage_proxy)

Added: Zope/hotfixes/tests/__init__.py
===================================================================

Added: Zope/hotfixes/tests/test_hotfix.py
===================================================================
--- Zope/hotfixes/tests/test_hotfix.py	2007-03-20 09:05:56 UTC (rev 73390)
+++ Zope/hotfixes/tests/test_hotfix.py	2007-03-20 09:09:02 UTC (rev 73391)
@@ -0,0 +1,141 @@
+import StringIO
+from Testing.ZopeTestCase import FunctionalTestCase, user_name, user_password
+
+class NoGETTest(FunctionalTestCase):
+    def afterSetUp(self):
+        self.folder_path = '/'+self.folder.absolute_url(1)
+        self.setRoles(('Manager',))
+        
+    def _onlyPOST(self, path, qstring='', success=200, rpath=None):
+        basic_auth = '%s:%s' % (user_name, user_password)
+        env = dict()
+        if rpath:
+            env['HTTP_REFERER'] = self.app.absolute_url() + rpath
+        response = self.publish('%s?%s' % (path, qstring), basic_auth, env)
+        self.assertEqual(response.getStatus(), 403)
+        
+        data = StringIO.StringIO(qstring)
+        response = self.publish(path, basic_auth, env, request_method='POST',
+                                stdin=data)
+        self.assertEqual(response.getStatus(), success)
+    
+    def test_userFolderAddUser(self):
+        path = self.folder_path + '/acl_users/userFolderAddUser'
+        qstring = 'name=foo&password=bar&domains=&roles:list=Manager'
+        self._onlyPOST(path, qstring)
+        
+    def test_userFolderEditUser(self):
+        path = self.folder_path + '/acl_users/userFolderEditUser'
+        qstring = 'name=%s&password=bar&domains=&roles:list=Manager' % (
+            user_name)
+        self._onlyPOST(path, qstring)
+        
+    def test_userFolderDelUsers(self):
+        path = self.folder_path + '/acl_users/userFolderDelUsers'
+        qstring = 'names:list=%s' % user_name
+        self._onlyPOST(path, qstring)
+        
+    def test_manage_setUserFolderProperties(self):
+        path = self.folder_path + '/acl_users/manage_setUserFolderProperties'
+        qstring = 'encrypt_passwords=1'
+        self._onlyPOST(path, qstring)
+        
+    def test_addUser(self):
+        # _addUser is called indirectly
+        path = self.folder_path + '/acl_users/manage_users'
+        qstring = ('submit=Add&name=foo&password=bar&confirm=bar&domains=&'
+                   'roles:list=Manager')
+        self._onlyPOST(path, qstring)
+        
+    def test_changeUser(self):
+        # _changeUser is called indirectly
+        path = self.folder_path + '/acl_users/manage_users'
+        qstring = ('submit=Change&name=%s&password=bar&confirm=bar&domains=&'
+                   'roles:list=Manager' % user_name)
+        self._onlyPOST(path, qstring)
+        
+    def test_delUser(self):
+        # _delUsers is called indirectly
+        path = self.folder_path + '/acl_users/manage_users'
+        qstring = ('submit=Delete&names:list=%s' % user_name)
+        self._onlyPOST(path, qstring)
+        
+    def test_manage_takeOwnership(self):
+        self.setRoles(('Owner',))
+        path = self.folder_path + '/acl_users/manage_takeOwnership'
+        rpath = self.folder_path + '/acl_users/manage_owner'
+        self._onlyPOST(path, success=302, rpath=rpath)
+       
+    def test_manage_changeOwnershipType(self):
+        self.setRoles(('Owner',))
+        path = self.folder_path + '/acl_users/manage_changeOwnershipType'
+        self._onlyPOST(path, success=302)
+        
+    def test_manage_setPermissionMapping(self):
+        path = self.folder_path + '/manage_setPermissionMapping'
+        qstring = 'permission_names:list=Foo&class_permissions:list=View'
+        self._onlyPOST(path, qstring)
+        
+    def test_manage_acquiredPermissions(self):
+        path = self.folder_path + '/manage_acquiredPermissions'
+        qstring = 'permissions:list=View'
+        self._onlyPOST(path, qstring)
+        
+    def test_manage_permission(self):
+        path = self.folder_path + '/manage_permission'
+        qstring = 'permission_to_manage=View&roles:list=Manager'
+        self._onlyPOST(path, qstring)
+        
+    def test_manage_changePermissions(self):
+        path = self.folder_path + '/manage_changePermissions'
+        self._onlyPOST(path)
+        
+    def test_manage_addLocalRoles(self):
+        path = self.folder_path + '/manage_addLocalRoles'
+        qstring = 'userid=Foo&roles:list=Manager'
+        self._onlyPOST(path, qstring)
+        
+    def test_manage_setLocalRoles(self):
+        path = self.folder_path + '/manage_setLocalRoles'
+        qstring = 'userid=Foo&roles:list=Manager'
+        self._onlyPOST(path, qstring)
+        
+    def test_manage_delLocalRoles(self):
+        path = self.folder_path + '/manage_delLocalRoles'
+        qstring = 'userids:list=Foo'
+        self._onlyPOST(path, qstring)
+        
+    def test_addRole(self):
+        # _addRole is called indirectly
+        path = self.folder_path + '/manage_defined_roles'
+        qstring = 'submit=Add+Role&role=Foo'
+        self._onlyPOST(path, qstring)
+        
+    def test_delRoles(self):
+        # _delRoles is called indirectly
+        path = self.folder_path + '/manage_defined_roles'
+        qstring = 'submit=Delete+Role&role=Foo'
+        self._onlyPOST(path, qstring)
+        
+    def test_DTMLMethod_manage_proxy(self):
+        self.folder.addDTMLMethod('dtmlmethod')
+        path = self.folder_path + '/dtmlmethod/manage_proxy'
+        qstring = 'roles:list=Manager'
+        self._onlyPOST(path, qstring)
+        
+    def test_PythonScript_manage_proxy(self):
+        from Testing.ZopeTestCase import installProduct
+        installProduct('PythonScripts')
+        dispatcher = self.folder.manage_addProduct['PythonScripts']
+        dispatcher.manage_addPythonScript('pythonscript')
+        path = self.folder_path + '/pythonscript/manage_proxy'
+        qstring = 'roles:list=Manager'
+        self._onlyPOST(path, qstring)
+
+def test_suite():
+    from unittest import makeSuite
+    return makeSuite(NoGETTest)
+
+if __name__ == '__main__':
+    import unittest
+    unittest.main()

Added: Zope/hotfixes/version.txt
===================================================================
--- Zope/hotfixes/version.txt	2007-03-20 09:05:56 UTC (rev 73390)
+++ Zope/hotfixes/version.txt	2007-03-20 09:09:02 UTC (rev 73391)
@@ -0,0 +1 @@
+Hotfix_20070320



More information about the Zope-Checkins mailing list