Zope sandbox escape via SecureModuleImporter from Products/PageTemplates/ZRPythonExpr.py

Bug #1047318 reported by Roman Fiedler
256
This bug affects 1 person
Affects Status Importance Assigned to Milestone
Zope 2
Fix Released
Undecided
Tres Seaver

Bug Description

During security testing, a privilege escalation flaw was found in Products/PageTemplates/ZRPythonExpr.py which allows to execute arbitrary commands on the host. The POC was mounted against an outdated internal machine with Zope-2.10.4-final, but it seems, that the problematic module loader code is still present in Zope2-2.13.16.

Zope-2.10.4-final/lib/python/Products/PageTemplates/ZRPythonExpr.py:
class _SecureModuleImporter:
    __allow_access_to_unprotected_subobjects__ = True

    def __getitem__(self, module):
        mod = safe_builtins['__import__'](module)
        path = module.split('.')
        for name in path[1:]:
            mod = getattr(mod, name)
        return mod

On zope.2.10.4, the _SecureModuleImporter class does not check, if the parameter "module" is really a basic string object and the returned object is a module or similar. If the function is called with a "module"-object extending the class "str" implementing a bogus split() method, the loader operates on different split results while traversing security checks and when using the split result to access the modules. By tricking the method to return a reference to inspect.getmembers, the returned reflection-function can be used to load any modules and access all functions without any security restrictions.

To deploy the POC, the user has to have privileges to create new zope pages using PageTemplates. The attached POC works only on linux systems and returns a archive containing all the data from /etc, but the function used (popen) could be easily used to run any command.

Since I have no recent zope.2.13 installation, could you please check if the bug is still present in 2.13. That would also show, that the privilege escalation is not only due to some bogus historic zope installation used for testing.

Revision history for this message
Tres Seaver (tseaver) wrote :

Missing an attachment containing the proof-of-concept.

Changed in zope2:
status: New → Incomplete
Revision history for this message
Roman Fiedler (roman-fiedler-deactivatedaccount) wrote :
Revision history for this message
Roman Fiedler (roman-fiedler-deactivatedaccount) wrote :
Revision history for this message
Roman Fiedler (roman-fiedler-deactivatedaccount) wrote :
Revision history for this message
Roman Fiedler (roman-fiedler-deactivatedaccount) wrote :

Sorry, too slow to collect files. To execute send 4 requests, therefore insert your infos into attachments from note 2, 3, 4
Send them in order 2, 3, 4, 3.

Last call should return a tar-archive of /etc folder on linux.

Changed in zope2:
status: Incomplete → New
Revision history for this message
Hanno Schlichting (hannosch) wrote :

The issue sounds like it's probably still present in Zope 2.12+. Once we get a patch for the issue I can release new 2.12 and 2.13 releases.

The issue doesn't warrant a hotfix as it's only related to restricted code. Restricted code doesn't actually protect against malicious users. It's only a best-effort attempt against introducing accidental security issues for semi-professional developers.

Revision history for this message
Tres Seaver (tseaver) wrote :

The vulnerability relies on first being able to do the following
within a PythonScript::

  import os

which should just fail, as there is no ModuleSecurityInfo for 'os'
in a default ZopeInstall.

The rest of the exploit (which I don't yet fully understand) seems
to hack how string splitting is done when checking access to members
of the imported module.

I plan to fix the first issue inside AccessControl.SecurityInfo.secureModule.

Changed in zope2:
assignee: nobody → Tres Seaver (tseaver)
status: New → In Progress
Revision history for this message
Tres Seaver (tseaver) wrote :
Revision history for this message
Tres Seaver (tseaver) wrote :
Revision history for this message
Tres Seaver (tseaver) wrote :

I have uploaded patches which correct the "import unprotected modules"
problem.

- Both patches should be applied the the Zope 2.12 branch. We can then
   release 2.12.24 normally from there.

- The first patch should be applied to the AccessControl 2.13 branch and its trunk.
  We need a new 2.13.10 release (plus maybe a 3.0.4?)

- The second patch should be applied to the Products.PythonScripts trunk. We
  then need a 2.13.1 release.

- Finally, we need to bump the versions.cfg on the Zope 2.13 branch and the
  trunk, and then make a 2.13.17 release.

Revision history for this message
Hanno Schlichting (hannosch) wrote :

Thanks Tres, I'll do all the releases during the weekend. Have to run now.

Revision history for this message
Tres Seaver (tseaver) wrote :

Roman, I recommend that your site upgrade to Zope 2.10.13, which has a number
of bugfixes (some security-related) over 2.10.4:

 http://old.zope.org/Products/Zope/2.10.13/CHANGES.txt/

I just verified that the patches apply without modification to 2.10.13:

 $ wget http://old.zope.org/Products/Zope/2.10.13/Zope-2.10.13-final.tgz
 $ tar xzf Zope-2.10.13-final.tgz
 $ cd Zope-2.10.13-final/lib/python
 $ patch -p1 < ~/projects/Zope/Z2/lp2057318-AccessControl.patch
 patching file AccessControl/tests/testModuleSecurity.py
 Hunk #1 succeeded at 42 with fuzz 2.
 patching file AccessControl/tests/testZopeGuards.py
 Hunk #1 succeeded at 499 (offset -103 lines).
 Hunk #2 succeeded at 529 (offset -103 lines).
 Hunk #3 succeeded at 540 with fuzz 2 (offset -103 lines).
 Hunk #4 succeeded at 671 (offset -104 lines).
 patching file AccessControl/SecurityInfo.py
 Hunk #1 succeeded at 207 (offset -4 lines).
 patching file AccessControl/ZopeGuards.py
 Hunk #1 succeeded at 321 (offset -8 lines).
 $ patch -p1 < ~/projects/Zope/Z2/lp2057318-Products.PythonScripts.patch
 patching file Products/PythonScripts/tests/testPythonScript.py

You'll need to tweak one break if you try to apply the first one to 2.10.4.

Revision history for this message
Hanno Schlichting (hannosch) wrote :

I've tested the patches against Zope and Plone and had to make to adjustments. For one Products.PythonScripts.standard didn't use the ModuleSecurityInfo API correctly. Thus functions like html_quote were suddenly disallowed. The example Script (Python) created by Zope 2.12 used that. So I fixed the API call.

The second problem relates to the safe_builtins from RestrictedPython or more specifically the utility_builtins. It's a small list of modules which are available as builtins. But with the tighter import checks, you could no longer import them. But since they were added to the __builtins__ list, you could just as well use them without any imports.

So I'll also allow imports for these. In AccessControl.__init__ I'll add:

for name in ('string', 'math', 'random', 'sets'):
    ModuleSecurityInfo(name).setDefaultAccess('allow')

ModuleSecurityInfo('DateTime').declarePublic('DateTime')

This matches the list from http://svn.zope.org/RestrictedPython/trunk/src/RestrictedPython/Utilities.py?rev=113154&view=markup. This change also avoids the test adjustments inside AccessControl for the math.ceil function.

Changed in zope2:
milestone: none → 2.13.17
status: In Progress → Fix Released
Revision history for this message
David Glick (davisagli) wrote :

In Zope 2.13.17 it seems to be no longer possible to import things from the ZTUtils module in restricted code, which is supposed to be allowed.

Revision history for this message
Tres Seaver (tseaver) wrote :

> In Zope 2.13.17 it seems to be no longer possible to import things
> from the ZTUtils module in restricted code, which is supposed to be allowed.

ZTUtils was missing the required ModuleSecurityInfo declaration, and
so became un-importable when the import restriction was tightened. The
2.12 and 2.13 bracnhes and the trunk have fixes for this issue.

Revision history for this message
David Glick (davisagli) wrote :

> ZTUtils was missing the required ModuleSecurityInfo declaration, and
so became un-importable when the import restriction was tightened. The
2.12 and 2.13 bracnhes and the trunk have fixes for this issue.

Thanks for pointing that out; I had missed the unreleased fix. But even with that, I'm seeing lots of Plone test failures due to not being able to access ZTUtils. Here is an example:

  File "/Users/davidg/.buildout/eggs/zope.tales-3.5.2-py2.7.egg/zope/tales/tales.py", line 696, in evaluate
    return expression(self)
   - file:/Users/davidg/Plone/p42_py27/src/Products.CMFPlone/Products/CMFPlone/skins/plone_templates/batch_macros.pt
   - Line 12, Column 0
   - Expression: <PythonExpr ztu.make_query>
   - Names:
      {'container': <PloneSite at /plone>,
       'context': <ATFolder at /plone/Members/test_user_1_/af>,
       'default': <object object at 0x10029f810>,
       'here': <ATFolder at /plone/Members/test_user_1_/af>,
       'loop': {},
       'nothing': None,
       'options': {'args': ()},
       'repeat': <Products.PageTemplates.Expressions.SafeMapping object at 0x10c60b050>,
       'request': <HTTPRequest, URL=http://nohost>,
       'root': <Application at >,
       'template': <FSPageTemplate at /plone/atct_album_view used for /plone/Members/test_user_1_/af>,
       'traverse_subpath': [],
       'user': <PloneUser 'test_user_1_'>}
  File "/Users/davidg/Plone/p42_py27/src/Zope2/src/Products/PageTemplates/ZRPythonExpr.py", line 48, in __call__
    return eval(self._code, vars, {})
   - __traceback_info__: ztu.make_query
  File "PythonExpr", line 1, in <expression>
Unauthorized: You are not allowed to access 'make_query' in this context

Revision history for this message
Tres Seaver (tseaver) wrote :

I just made an instance from the tip of the 2.13 branch::

 $ cd projects/Zope/Z2/Zope-2.13- branch
 $ bin/mkzopeinstance -d /tmp/z213 -u admin:123
 $ /tmp/z213/bin/zopectl fg

and was able to access ZTUtils.make_query from both a PythonScript
and a PageTemplate.

Revision history for this message
David Glick (davisagli) wrote :

I will poke around some more. I suspect it may be a problem related to test setup.

Revision history for this message
David Glick (davisagli) wrote :

Never mind, it was a Plone-specific problem. Someone else had tried to solve the problem by moving the ZTUtils import into unrestricted code, but then the ModuleSecurityInfo wasn't getting applied.

Revision history for this message
Hanno Schlichting (hannosch) wrote :

David: Do you think that was the only issue? Or: Should I release new versions of Zope2 now?

Revision history for this message
David Glick (davisagli) wrote :

The Plone tests are passing now with a Zope2.13 checkout, so I think you can make releases. There was one other problem -- with doing "from StringIO import StringIO", but that was because Plone was calling allow_class for StringIO but not allowing the module.

Tres Seaver (tseaver)
information type: Private Security → Public Security
To post a comment you must log in.
This report contains Public Security information  
Everyone can see this security related information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.