-
-
Notifications
You must be signed in to change notification settings - Fork 195
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added security checks for str.format. Part of PloneHotfix20170117.
- Loading branch information
1 parent
c60a39b
commit a7d4769
Showing
8 changed files
with
315 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,7 +29,9 @@ | |
|
||
if "text/html" not in context.REQUEST.getHeader('Accept', ''): | ||
context.REQUEST.RESPONSE.setHeader("Content-Type", "application/json") | ||
return '{{"error_type": "{0:s}"}}'.format(error_type) | ||
# Note: using %s instead of .format to avoid possibly expensive guarded | ||
# attribute check. | ||
return '{{"error_type": "%s"}}' % error_type | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
mauritsvanrees
Author
Member
|
||
|
||
if error_log_url: | ||
error_log_id = error_log_url.split('?id=')[1] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
<p tal:content="python:('%s' % context).lower()" /> | ||
<p tal:content="python:(u'%s' % context).upper()" /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
from plone.app.testing import login | ||
from plone.app.testing import logout | ||
from plone.app.testing import setRoles | ||
from plone.app.testing import TEST_USER_ID | ||
from plone.app.testing import TEST_USER_NAME | ||
from Products.CMFPlone.tests.PloneTestCase import PloneTestCase | ||
from zExceptions import Unauthorized | ||
|
||
|
||
BAD_STR = """ | ||
<p tal:content="python:'class of {0} is {0.__class__}'.format(context)" /> | ||
""" | ||
BAD_UNICODE = """ | ||
<p tal:content="python:u'class of {0} is {0.__class__}'.format(context)" /> | ||
""" | ||
GOOD_STR = '<p tal:content="python:(\'%s\' % context).lower()" />' | ||
GOOD_UNICODE = '<p tal:content="python:(\'%s\' % context).lower()" />' | ||
AQ_TEST = """ | ||
<p tal:content="python:\'parent of {0} is {0.aq_parent}\'.format(context)" /> | ||
""" | ||
|
||
|
||
def noop(context=None): | ||
return lambda: context | ||
|
||
|
||
def hack_pt(pt, context=None): | ||
# hacks to avoid getting error in pt_render. | ||
pt.getPhysicalRoot = noop() | ||
pt._getContext = noop(context) | ||
pt._getContainer = noop(context) | ||
pt.context = context | ||
|
||
|
||
class TestSafeFormatter(PloneTestCase): | ||
"""The the safe formatter. | ||
This is from PloneHotfix20170117. | ||
""" | ||
|
||
def test_cook_zope2_page_templates_bad_str(self): | ||
from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate | ||
pt = ZopePageTemplate('mytemplate', BAD_STR) | ||
hack_pt(pt) | ||
self.assertRaises(Unauthorized, pt.pt_render) | ||
|
||
def test_cook_zope2_page_templates_bad_unicode(self): | ||
from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate | ||
pt = ZopePageTemplate('mytemplate', BAD_UNICODE) | ||
hack_pt(pt) | ||
self.assertRaises(Unauthorized, pt.pt_render) | ||
|
||
def test_cook_zope2_page_templates_good_str(self): | ||
from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate | ||
pt = ZopePageTemplate('mytemplate', GOOD_STR) | ||
hack_pt(pt) | ||
self.assertEqual(pt.pt_render().strip(), '<p>none</p>') | ||
|
||
def test_cook_zope2_page_templates_good_unicode(self): | ||
from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate | ||
pt = ZopePageTemplate('mytemplate', unicode(GOOD_UNICODE)) | ||
hack_pt(pt) | ||
self.assertEqual(pt.pt_render().strip(), '<p>none</p>') | ||
|
||
def test_cook_zope2_page_templates_aq_parent(self): | ||
# Accessing aq_parent should be allowed normally. | ||
from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate | ||
pt = ZopePageTemplate('mytemplate', AQ_TEST) | ||
hack_pt(pt, context=self.portal) | ||
self.assertEqual( | ||
pt.pt_render().strip(), | ||
u'<p>parent of <PloneSite at plone> is ' | ||
u'<Application at ></p>') | ||
|
||
def test_access_to_private_content_not_allowed_via_rich_text(self): | ||
try: | ||
# This is only available for tests if we have plone.app.dexterity, | ||
# which in tests is by default only the case for Plone 5. | ||
from plone.app.textfield.value import RichTextValue | ||
except ImportError: | ||
return | ||
from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate | ||
setRoles(self.portal, TEST_USER_ID, ['Manager']) | ||
login(self.portal, TEST_USER_NAME) | ||
wf_tool = self.portal.portal_workflow | ||
wf_tool.setChainForPortalTypes( | ||
['Document'], 'simple_publication_workflow') | ||
self.portal.invokeFactory('Document', 'foobar') | ||
foobar = self.portal.foobar | ||
foobar.text = RichTextValue(u'Secret.', 'text/plain', 'text/html') | ||
self.assertEqual( | ||
self.portal.portal_workflow.getInfoFor(foobar, 'review_state'), | ||
'private') | ||
logout() | ||
pt = ZopePageTemplate('mytemplate', ''' | ||
<p tal:content="structure python:'access {0.foobar.text.output}'.format(context).lower()" /> | ||
''') # noqa | ||
hack_pt(pt, context=self.portal) | ||
self.assertRaises(Unauthorized, pt.pt_render) | ||
|
||
def test_access_to_private_content_not_allowed_via_any_attribute(self): | ||
# This is a more general version of the rich text one. | ||
from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate | ||
setRoles(self.portal, TEST_USER_ID, ['Manager']) | ||
login(self.portal, TEST_USER_NAME) | ||
wf_tool = self.portal.portal_workflow | ||
wf_tool.setChainForPortalTypes( | ||
['Document'], 'simple_publication_workflow') | ||
self.portal.invokeFactory('Document', 'foobar') | ||
foobar = self.portal.foobar | ||
self.assertEqual( | ||
self.portal.portal_workflow.getInfoFor(foobar, 'review_state'), | ||
'private') | ||
logout() | ||
# If access to context.foobar.Title was allowed, it would still only | ||
# say 'bound method ATDocument.Title', without giving the actual title, | ||
# but there may be other attributes that give worse info. | ||
pt = ZopePageTemplate('mytemplate', ''' | ||
<p tal:content="structure python:'access {0.foobar.Title}'.format(context)" /> | ||
''') | ||
hack_pt(pt, context=self.portal) | ||
self.assertRaises(Unauthorized, pt.pt_render) | ||
|
||
# Zope 3 templates are always file system templates. So we actually have | ||
# no problems allowing str.format there. | ||
|
||
def test_cook_zope3_page_templates_normal(self): | ||
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile | ||
# Note: on Plone 3.3 this is actually a ZopeTwoPageTemplateFile. | ||
pt = ViewPageTemplateFile('normal_zope3_page_template.pt') | ||
hack_pt(pt) | ||
# Need to pass a namespace. | ||
namespace = {'context': self.portal} | ||
self.assertEqual( | ||
pt.pt_render(namespace).strip(), | ||
u'<p><plonesite at plone></p>\n' | ||
u'<p><PLONESITE AT PLONE></p>') | ||
|
||
def test_cook_zope3_page_templates_using_format(self): | ||
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile | ||
# Note: on Plone 3.3 this is actually a ZopeTwoPageTemplateFile. | ||
pt = ViewPageTemplateFile('using_format_zope3_page_template.pt') | ||
hack_pt(pt) | ||
# Need to pass a namespace. | ||
namespace = {'context': self.portal} | ||
self.assertEqual( | ||
pt.pt_render(namespace).strip(), | ||
u"<p>class of <plonesite at plone> is " | ||
u"<class 'products.cmfplone.portal.plonesite'></p>\n" | ||
u"<p>CLASS OF <PLONESITE AT PLONE> IS " | ||
u"<CLASS 'PRODUCTS.CMFPLONE.PORTAL.PLONESITE'></p>") | ||
|
||
def test_standard_error_message(self): | ||
# In Plone 5.0 standard_error_message.py has: | ||
# if "text/html" not in context.REQUEST.getHeader('Accept', ''): | ||
# return '{{"error_type": "{0:s}"}}'.format(error_type) | ||
# | ||
# So if there is an error and the request does not accept html, then | ||
# str.format is used. We don't want this to fail with an Unauthorized. | ||
|
||
response = self.publish( | ||
'/plone/standard_error_message', | ||
env={'HTTP_ACCEPT': 'application/json'}) | ||
|
||
# This should *not* return a 302 Unauthorized. We expect a 404. Or | ||
# really a 200, because we explicitly call the standard_error_message | ||
# page and this is correctly rendered. | ||
self.assertTrue(response.status in (200, 404)) | ||
# We expect a json string back. | ||
self.assertTrue(response.body, '{"error_type": "None"}') | ||
|
||
def test_resource_registry_vector(self): | ||
for vector in ('less-variables.js', 'less-modify.js'): | ||
src = ''' | ||
class ctx: | ||
def format(self, *args, **kwargs): | ||
self.foo=context | ||
return "foo" | ||
context.portal_registry['plone.lessvariables']['foo'] = ctx() | ||
context.portal_registry['plone.lessvariables']['bar'] = "{foo.foo.__class__}" | ||
js = context.restrictedTraverse("%s") | ||
return js() | ||
''' % vector | ||
from Products.PythonScripts.PythonScript import PythonScript | ||
script = PythonScript('evil') | ||
script._filepath = 'evil' | ||
script.write(src) | ||
self.portal.evil = script | ||
output = self.publish('/plone/evil') | ||
self.assertFalse( | ||
'Products.CMFPlone.Portal.PloneSite' in output.body) | ||
|
||
def test_positional_argument_regression(self): | ||
""" | ||
to test http://bugs.python.org/issue13598 issue | ||
""" | ||
from Products.CMFPlone.utils import SafeFormatter | ||
try: | ||
self.assertEquals( | ||
SafeFormatter('{} {}').safe_format('foo', 'bar'), | ||
'foo bar' | ||
) | ||
except ValueError: | ||
# On Python 2.6 you get: | ||
# ValueError: zero length field name in format | ||
pass | ||
|
||
self.assertEquals( | ||
SafeFormatter('{0} {1}').safe_format('foo', 'bar'), | ||
'foo bar' | ||
) | ||
self.assertEquals( | ||
SafeFormatter('{1} {0}').safe_format('foo', 'bar'), | ||
'bar foo' | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
<p tal:content="python:'class of {0} is {0.__class__}'.format(context).lower()" /> | ||
<p tal:content="python:u'class of {0} is {0.__class__}'.format(context).upper()" /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@mauritsvanrees this change makes this return invalid JSON, since the double brackets are now rendered literally rather than serving as escapes for
.format