tips on securing zope, was Re: [Zope] Zope inserting base tag

Jamie Heilman jamie@audible.transient.net
Thu, 27 Feb 2003 17:29:03 -0800


Dylan Reinhardt wrote:
> At 01:39 PM 2/27/2003, Jamie Heilman wrote:
> >Pragmatically this is the same as HTML quoting.  (Thats not always the
> >case unfortunately.)
> 
> Could you offer an example where &dtml-some_var; returns something 
> different from <dtml-var some_var html_quote>?

I'm not saying they do, afaik, they don't.  I'm saying sometimes,
depending on how much you want to play guardian, escaping < > & and "
or ' may not be "enough" to prevent hanky-panky.
 
> I read your post on VHM exploits a couple weeks ago.  Is this the scope of 
> the problem?

Not entirely, but it is related.

> Is the problem solved by using a proxy cache to drop any requests
> that contain the magic VHM-related strings?  Or does it go deeper?

It goes deeper.  Actually using a VHM is a good prevention technique.
Andrew Athan made an observation last week that woke me up to a whole
new problem.

> Also, how does using &dtml-URL1; do anything to guard against this?

It will ensure special characters injected via the URI are quoted
ostensibly preventing cross site scripting attacks.

> Won't URL1 resolve to what follows VirtualHostBase in either syntax?

Only if you use a VHM.  Many don't.

> I've got a HOWTO that includes information on virtual hosting...
> I'll be sure to add this information and any other advice or insight
> you're able to offer.

OK, here's a breakdown of the problems, both immediate, and
theoretical.  First the immediate.  There exist, in both the 2.6.1 and
the 2.7 CVS code, and afaik, all previous code, several spots where
anonymous clients may inject markup which will appear in a document
with no escaping.  I'm not going to go into a huge explanation about
why this is bad, its called cross site scripting, its been a problem
with web content from day 1, anyone who doesn't know what this is by
now can use their favorite search engine to find out.  Cache poisoning
lets us inject markup in one transaction and have that markup persist
across several transactions with completely unrelated clients.

I warned earlier about the dangers of not protecting against anonymous 
requests for resources that contained VHM's "magic" keywords in them.
(/VirtualHostBase/, /VirtualHostRoot/, and /_vh_ -- though let me just
note that there's an extremely good argument for blocking all requests
that contain any path element that beings with _ because so many
products are prone to using that as a special pre-traversal hook;
for example disabling access rules.)  Well it turns out another avenue
of attack is available via the Host: header of HTTP 1.1 requests.
People who don't use a VHM are especially vulnerable.  People who
attempt to use a single rewrite-rule to lazily do vhosting for
multiple domains may also be at risk (this was discussed a little on
zope-dev a few weeks ago).

Andrew Athan noted that a bug in an IE beta was screwing up his site
where absolute_url() was being used.  This is specifically because of
an incorrect Host: header, and thats what tipped me off to the
problem.  So I sent a request to a (bare) ZServer instance I had
running with a host header of www.<b>old-flava'-&-"kids".com.  The
most immediate thing I noticed is that the injected base tag was:
<base href="http://www.<b>old-flava'-&-"kids".com/path" />

No escaping[1].  This could be disastrous for sites fronted by a cache!
I thought.  But then upon experimenting further it became obvious that
for RAM Cache users it wouldn't be a poisoning[2] problem though,
because the base tag is added dynamically on every request, and never
makes it into the ram cache.  However, external caches *may* be
vulnerable, if they don't use the Host: header as part of their
caching key (in theory anyway).  I haven't tested that, I am
interested in the results if anyone does.  I don't use squid or other
proxy caches, I don't know what all they use to key content, or if its
configurable.

The bad news is that its not just the base tag.  For example, Image.py
is vulnerable[3].  There are probably more.  Protecting against this
is possible by using a VHM defensively.  The Host: header is
overridden by a VHM instance's VirtualHostBase hook.  Provided you
hardcode the URI used by your proxy config, you should be safe.  For
example, with apache rewrite rules:

RewriteEngine on
RewriteCond %{REQUEST_URI} /_ [OR]
RewriteCond %{REQUEST_URI} /VirtualHostBase/ [OR]
# possibly other things zope shouldn't expose but does[4]
RewriteCond %{REQUEST_URI} /VirtualHostRoot/
RewriteRule .* - [F,L]
RewriteRule ^/foo(.*) http://127.0.0.1:8080/VirtualHostBase/http/example.com:80/example/VirtualHostRoot/_vh_foo$1 [P,L]

Note how example.com is hardcoded and the example doesn't rely on any
tricks with HTTP_HOST.  That is important.  (see Oliver Bleutgen and
my own comments on zope-dev 2 weeks ago for why)  This effectively
lets Apache handle HTTP 1.1's name based virtual hosting, and takes the
burden off Zope, as well as protecting you against Host header
treachery.  I bet the same technique is applicable to squid and other
proxy servers.

The above protection technique is important for other reasons, which
bear discussion.  Cross site scripting is bad, but its only one avenue
of attack.  As it currently stands you could poison a cache by doing
something like:

telnet victim.example.com 80
GET / HTTP/1.1
Host: attacker.example.org

Once victim.example.com's cache is poisoned, assuming the attacker
controls attacker.example.org, they could easily change the images a
browser would likely see when visiting victim.example.com.  What was
once rendered as:
<img src="http://victim.example.com/company_logo.jpg" ... />
is now rendered as
<img src="http://attacker.example.org/company_logo.jpg" ... />

Instant remote defacement.  I'll let you use your imagination from
here on the other directions this can lead.  Point is, you don't have
to use cross site scripting to be evil, so even once we ferret out and
crush all the spots in the zope code that allow xss, there's still
this crap to worry about.

Solving this issue is sorta ugly.  Apache's UseCanonicalName defaults
to "on" for a very good reason.  For now fronting zope with a trusted
proxy is the best workaround I know of.

Thus ends today's session of my tips on securing zope.  (oh, btw, this
is bug 813 in the collector, though as of this writting, its still not
public)

-- 
Jamie Heilman                   http://audible.transient.net/~jamie/
"...thats the metaphorical equivalent of flopping your wedding tackle 
 into a lion's mouth and flicking his lovespuds with a wet towel, pure 
 insanity..."						-Rimmer

[1] a patch:
--- lib/python/ZPublisher/HTTPResponse.py       Sat Feb  1 00:25:42 2003
+++ taste/the/fun/HTTPResponse.py       Tue Feb 25 20:06:10 2003
@@ -453,7 +453,8 @@
                     ibase = base_re_search(body)
                     if ibase is None:
                         self.body = ('%s\n<base href="%s" />\n%s' %
-                                   (body[:index], self.base, body[index:]))
+                                     (body[:index], self.quoteHTML(self.base),
+                                      body[index:]))
                         self.setHeader('content-length', len(self.body))
 
     def appendCookie(self, name, value):

[2] It should be noted, while cache poisoning with cross site
    scripting is an ugly combination, removing the cache poisoning from
    the equation isn't really a complete solution.  It does increase the
    difficulty exploit too a degree, but being able to inject markup into
    a page is always a problem, regardless if the markup is persistent
    or not.

[3] No patch.  I refuse to touch Image.py, as it stands its a total
    train wreck.  Sorry.

[4] Does anyone know how to determine every valid object within the
    entire local, acquisition, or otherwise context?  This would help
    expose things like misc_, p_, HelpSys, Redirect and friends so
    that the admin could have some control over what they published.
    As it stands there entirely too much magic "invisible" shit just
    floating in the acquisition path waiting for abuse, and every
    product you install just makes it worse.  A tool to dump entire
    namespaces is something I desperately want because auditing the
    entire code base just isn't practical.