Minh’s Notes

Human-readable chicken scratch

Minh Nguyễn
February 15th, 2009
AVIM
#2,062

Edit

Wrong

First things first: if you use version 20080728.280 of my AVIM extension, upgrade to version 20080728.306 now.

Last Friday, Adblock Plus developer Wladimir Palant refuted five typical excuses for calling the eval() function in JavaScript. I remembered that function well: take any string, pass it into eval(), and the string gets executed as though it were ordinary code. When I took Stanford’s hacking class last spring, we developed an exploit that targeted a fictitious website’s generous use of the function. eval() is the most easily abused function available to JavaScripters, because it’s such a tantalizing shortcut. Why bother learning DOM Level 3 when you can call one function and move on?

Were you to conduct a comprehensive survey of computer programmers, I’d suspect that nearly all of us would rate ourselves “above average” programmers who keep particularly good best practices in mind at all times. Like, to avoid eval() at all costs. But I called that function – once – and Wladimir caught me.

The gory technical details go something like this: Mozilla-based applications like Firefox have user interfaces built in an XML-based language called XUL. Textboxes in XUL look kind of like textboxes in HTML: <textbox value="foobar" />.

JavaScript makes the interface interactive, just as it does for webpages. If you want the textbox to do something special as soon as you change its contents, you use the oninput attribute:

<textbox value="foobar" oninput="this.value = 'barbaz';" />

This textbox changes its text to say “barbaz” every time you try to type or paste something in.

Since AVIM is an IME, it similarly changes the textbox’s contents when you type in it. But for various reasons, the oninput attribute has no effect when AVIM interferes with the text. That prevents the interface from responding properly to input in places like the Find Bar and the Library (formerly the Bookmarks Organizer).

So what’s my solution? After searching for low-level, implementation-detail functions to do what I want, I settle on calling – naturally – eval() with the contents of the oninput attribute. After some quick testing to verify that it works, I ship a new version of AVIM. To make myself feel better about knowingly using such a dangerous function, I added this comment above the call to eval():

// Truly awful kludge

Indeed. The problem is that, like all extensions, AVIM is given more privileges than an ordinary webpage script. It can access APIs for everything from opening dialog boxes to saving files. With enough effort, you might even be able to get files to execute. Such privileges aren’t usually a problem, since malicious and malware-laden extensions rarely go public on the official Firefox Add-ons website.

But the eval() function doesn’t know where the string you’re giving it came from. That string may come from a webpage that contains a textbox with the oninput attribute. eval() just assumes you wanted it to execute exactly what you, the extension, told it to. So the string gets executed within the extension’s scope, with all the extension’t privileges, and with access to plenty of Mozilla’s low-level interfaces.

As a concrete example, what do you think would happen if a webpage contained this HTML textbox:

<input oninput="delete avim;" />

Never mind that oninput isn’t a standard HTML attribute: my extension never checked, and eval() certainly couldn’t be bothered to. If you have AVIM enabled and start typing in this textbox, AVIM winds up disabling itself.

Cool, huh? One could imagine doing a bit more with that line of attack.

After Wladimir wrote about eval(), I spent several hours searching for more low-level functions that would execute the oninput code in a safer way, but nothing seemed clean or sane enough to actually use. As usual the solution turned out simpler than I ever imagined: generate a fake onInput event.

So, yeah: upgrade now, pretend I never made this mistake, and hope you never do either.

This post was embargoed for eight days to give AVIM’s users adequate time to upgrade.