allowing it access to various language and browser features.
Sometimes you would like to allow third-party code to run as part of a page or application. Unfortunately, it's hard to know what nefarious things someone else's code might do. Nerio defines a subset of JavaScript and provides some simple tools for checking that an arbitrary piece of JavaScript code is valid Nerio code. Static analysis has the benefit that once code is checked, it can run at full speed whether in the browser or a server-side engine. Alternatives involving JavaScript interpreters compiled to asm.js can be two orders of magnitude slower. WebWorker sandboxes require a virtual DOM, which is also potentially very slow.
Nerio provides a simple command line tool as well as several programmatic interfaces for statically checking fragments of JavaScript in memory or files. Nerio checks for use of features or APIs that might enable exfiltration of private data, communication with untrusted servers or attempts to interpose on core browser APIs. Much like a compiler, Nerio can provide detailed line-level warnings and errors when it detects unsafe code. As a pure JavaScript implementation, the entire tool and API can be used from a web page, or in standalone applications via node.js.
Nerio is implemented entirely in JavaScript. The excellent Esprima parsing engine is used to parse raw JavaScript code into the standard Mozilla SpiderMonkey AST data structure. esmangle is used to "compress" (remove redundancy, dead code, and normalize syntax in) the AST structure. Nerio then makes several passes over the AST looking for function call sites, identifiers, and symbols that look suspicious. Nerio does not fail early: instead it looks at all of the code and reports any problems found, which is potentially useful for debugging repeated violations or to derive a sense of just how problematic the code is. Internally, Nerio is designed to be modular: modules that implement different checks register themselves with a central engine which orchestrates their execution over the AST and collects violations in a standard way. We are also working on some experimental code-rewriting features in which we rewrite the AST on the fly to wrap suspicious code in runtime checks and then use escodegen to generate JavaScript from the transformed AST.
We inspect a number of language and browser API features. Our approach is to compress/optimize the AST before searching for problematic features so that the number of cases (or corner cases) is minimized. JavaScript's syntactic flexibility can manifest use of one feature in the same way as several different ASTs, compressing the AST generally converts all instances to a single, optimal structure.
Language
eval()
enables arbitrary code execution.- Use of
obj[key]
object access when obj is special (see below) and key is not literal.
Browser API
References to window
, XMLHttpRequest
, document
, and WebWorker
.
The above global objects or classes enable manipulation of the DOM, direct or indirect initiation of network requests (think image src
attribute) or create new code execution flows. While this might seem like a short list, these features and API objects represent most of the surface that a potentially malicious script could make use of. As it turns out, detecting potential references to these objects and functions in JavaScript is fairly challenging.
There is a substantial body of work related to Nerio, from the research community as well as industry and open source. Below is a brief summary of several key projects. Gatekeeper in particular enumerates a lot of the JavaScript language and browser API features that we are interested in restricting or disabling altogether. Treehouse provides a lightweight virtual DOM and runtime isolation (via WebWorkers) that we will likely combine with Nerio in future work.
Virtual runtimes
js.js - GitHub page
A JS interpreter in JS, so you can sandbox while you sandbox.
Treehouse - ATC '12 paper
Loads a "broker" into a WebWorker. Interposes on browser APIs, freezes them, then runs code against a virtual DOM via message passing with main thread.
Static analysis
FBJS - Developer page
Facebook App JS subset (proprietary)
Gatekeeper - USENIX SEC '09 paper
Static analysis + code rewriting to safe subset of JS
Adsafety - Website
Verifying static checkers (in this case Adsafe)
Adsafe - adsafe.org (GitHub page)
A subset of JS safe for embedding in ads
Browsershield - OSDI '06 paper
Complete JS/HTML rewriting to safe widgets
Websandbox - websandbox.org
Similar to Browsershield (also by MS), rewrites all web content into safe widgets
Caja - Google Code
Ca-pabilities based Ja-vascript. Weird, supposed to pronounce as “Caha” from the Spanish “safe” or “box”. Rewrites html, css, JavaScript to a safe version.
So far Nerio is a humble prototype, only a shadow of what we have planned for it. That said, the flexibility and effectiveness of our prototype is promising and we hope to continue building it out into a more sophisticated and practicable tool as future work. Static analysis at the AST level is quite difficult, and especially so for a dynamic, mixed-paradigm language like JavaScript. We hope that eventually Nerio will enable a class of browser-based applications that can make stronger guarantees about where our personal data can, and cannot, end up.
To get started with Nerio you should have the latest Node.js (including npm
) installed on your system. There is a devenv
script included that will install it for you on unix-like platforms. Note: you need to download node
and npm
directly from nodejs.org because the version tracked by most package managers is too old.
npm install -g nerio
Then you can use nerio
on the command line,
$ echo "var PI = 3.14;" | nerio
or pass it a file to check:
$ nerio samples/eval.js
Failed:
EVAL: Explicit call to eval() at 2:0
EVAL: Assignment to result of eval() at 4:8
EVAL: Assignment to result of eval() at 4:22
...
or the programmatic API:
nerio = require('nerio');
nerio.check_code(['js/script-to-check.js'], function(success, err) {
if( !success )
console.log(err);
else
console.log('Success!');
});
There is also an API compiled for use directly in the browser.
<script src="//nerio/dist/nerio.min.js"></script>
<script>
var code = '...';
nerio.check_code(code, function(success, err) { ... });
</script>