[Zope3-dev] Security: getAdapter(self, ISomething) is bad
Steve Alexander
steve@cat-box.net
Mon, 07 Apr 2003 13:03:31 +0200
Most calls to getAdapter (or queryAdapter) are made on a
content-object's behalf.
adapter = getAdapter(obj, ISomething)
In this case, 'obj' is usually security-proxied and context-wrapped.
However, if I write a content object that calls getAdapter for itself,
the 'self' reference to itself will be entirely unwrapped.
(Unless the method is a ContextMethod or called via a ContextProperty,
or the object's class derives from ContextAware, in which case the
'self' argument will be context-wrapped but not security-proxied.)
So, the adapter will be passed an unwrapped object as its context.
A malicious adapter implementation can access and set attributes on this
object that would normally not be allowed.
Marius and I have altered Step 7 of the Contact example to demonstrate this.
RCS file:
/cvs-repository/Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step7/contact.py,v
retrieving revision 1.2
diff -u -r1.2 contact.py
--- src/zopeproducts/contact/contact.py 7 Apr 2003 10:53:17 -0000 1.2
+++ src/zopeproducts/contact/contact.py 7 Apr 2003 10:54:12 -0000
@@ -4,7 +4,7 @@
from zope.app.interfaces.container import IAdding
from zope.app.event import publish
from zope.app.event.objectevent import ObjectCreatedEvent
-from zope.component import getUtility
+from zope.component import getUtility, getAdapter
class Contact (persistence.Persistent):
"""Personal contact information
@@ -22,6 +22,7 @@
return "%s %s" % (self._first, self._last)
def first(self):
+ info = getAdapter(self, IPostalInfo)
return self._first
def last(self):
@@ -74,6 +75,10 @@
__used_for__ = IContactInfo
def __init__(self, contact):
+ if type(contact) is Contact:
+ # Muhahaha. Evil adapter has access to a naked contact.
+ contact._first = "I STOLE YOUR IDENTITY"
+ contact._last = "YOU ARE 0WN3D"
self._contact = contact
lookup = getUtility(contact, IPostalLookup)
info = lookup.lookup(contact.postal_code())
In this example, someone has (for benign but, in this case, not
immediately useful reasons) added a 'getAdapter(self, IPostalInfo)' call
to the 'first' method of Contact.
Someone else has installed a malicious IPostalInfo adapter that looks
for an unproxied context, and sets some attributes that it should not be
allowed to.
It is unclear how best to solve this.
Perhaps getAdapter and queryAdapter should either raise an error if you
try to adapt something that is not security-proxied. (This will, of
course, break unit tests...)
Or, perhaps getAdapter and queryAdapter should add a security-proxy
around the object before it is passed to the adapter factory.
In either case, this adds a dependency between the component
architecture package and the security proxy package.
There is only one place in the Zope 3 source (at present) that adapts
from self: the _decode method of BrowserRequest in zope/publisher/browser.py
def _decode(self, text):
"""Try to decode the text using one of the available
charsets.
"""
if self.charsets is None:
envadaptor = getAdapter(self, IUserPreferredCharsets)
self.charsets = envadaptor.getPreferredCharsets()
for charset in self.charsets:
try:
text = unicode(text, charset)
break
except UnicodeError:
pass
return text
In this case, the IUserPerferredCharsets adapter cannot be overridden
locally because no local context is given. It is not clear how to handle
local vs global adapters in the case of the request.
--
Steve Alexander