Tuesday, June 17, 2008

Improving security of JSONP in Chrome

Injecting scripts in chrome are raising security concerns, and JSONP is just that - injecting JavaScript. As I pointed out in Make JSONP Work in Chrome, number of security concerns are arising from the fact that, in chrome, scripts have as much privileges as the user running the browser. That means that if malicious code is injected by JSONP provider it could access private and sensitive data or do some real damage to the system as I will show later on with couple of examples.
Unfortunately Firefox is not offering much control over the privileges of the script execution: in chrome script has all the privileges. netscape.security.PrivilegeManager.disablePrivilege is not working and, correct me if I'm wrong, all you have as alternative is to run the code in the sandbox, where you have limited privileges and again no way to control them. I would much more prefere to have sandbox privileges by default and ask for more privileges I require.

Enough ranting and lets show the code. Here is the old version of the code:


var scriptCollection = document.getElementsByTagName("script");
var reader = new httpReader(scriptCollection[scriptCollection.length - 1].src);

function evaluateJs(aReader){
if(aReader && aReader.mData) {
eval(aReader.mData);
}
}

reader.AsyncLoad(bind(evaluateJs, this, reader));

Basically the first part is getting the URL of the JSONP script to execute - jQuery AJAX function hack. The second one is the function that will evaluate received script. As I pointed out earlier, by doing this the received script is executed in chrome with all the privileges and that is not very good idea if you have internet server serving you the script.

Here is improved script to execute this code in the sandbox:


var scriptCollection = document.getElementsByTagName("script");
var reader = new httpReader(scriptCollection[scriptCollection.length - 1].src);

function runInSandbox(scriptToEval){
//this line is for testing purposes this privilege will be enabled in chrome anyway
//netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var safewin = new XPCNativeWrapper(window);

var sandbox = Components.utils.Sandbox(safewin);
sandbox.window = safewin;
sandbox.document = sandbox.window.document;
sandbox.__proto__ = safewin;

//add jsonp method to the safe window
for(var prop in window){
if(typeof window[prop] == "function"
&& prop.substring(0, 5) == "jsonp"){
sandbox.window[prop] = window[prop];
break;
}
}

Components.utils.evalInSandbox(scriptToEval, sandbox);
}

function evaluateJs(aReader){
if(aReader && aReader.mData) {
//eval(aReader.mData);
runInSandbox(aReader.mData);
}
}

reader.AsyncLoad(bind(evaluateJs, this, reader));

This is significantly longer but the only change is usage of runInSandbox function. This code is inspired by Greasemonkey implementation. We are creating safe window using XPCNativeWrapper. Using the safe window we are creating sandbox and setting sandbox properties - window, document and __proto__. The XPCNativeWrapper is wrapping the object and limiting access to its properties and functions. Mainly, it is not allowing access to added or changed properties and functions of the wrapped object. Look at: How the History of Greasemonkey Security Affects You Now for more informati on about possible attacks and pitfalls.
Since after wrapping the window the JSONP function is missing we are adding it by cycling through all the properties of the original window and upon finding the right function we are adding it to the safe window. We know that jQuery created the JSONP function so it is pretty safe to do this. After that evalInSandbox can be called and we have our JSONP function executed in sandbox without possiblity to do any damage.
To illustrate what's been shown so far I will create a couple of simple tests.

Simplistic injection testing

First of all to note that all of this I am showing is done by opening XUL window in Firefox, feel free to wrap it up into extension and test it properly.
Lets prepare our testing ground for exploring vulnerabilities arising. The full source code is here. Please put injection*.js files somewhere on your local web server if you have it so you can access it easily. They are simulating JSONP scripts injection and basically they are simulating what some malicious server might serve to our extension.
The injection1.js is simply showing alert window so we know that we have access to injection files.
Injection2.js is reading C:\temp\security.txt file from the disk (you create some test file) and showing its contents. It is there just to prove the point, it might easily delete your system files if you are running Firefox as admin, or read your Gmail username and password and send it to some 3rd party via web service, etc; the point is it can do anything if called from chrome.
Injection3.js is trying to add click event handler to existing window and attempt to make malicious script pass boundaries of the sandbox.
Injection4.js is using some function we deliberately made unsafe to make the point that even when running in sandbox we have to keep in mind what we are doing and that we might cause voulnerabilities ourselves.
The securityText.xul has 5 buttons and they are doing the following stuff:
Simple test - loads injection1.js - shows alert 'injection', if it shows alert we are injecting scripts as expected
Voulnerability! - executes Injection2.js in chrome and is reading content of C:\temp\security.txt
Safe 1! - executes Injection2.js in sandbox - showing that it will not read the file
Safe ! - on click changes txt to still safe - we are attaching malicious code to click event of the button, but since it is executed on the wrapper it will not read the file
Safe (not really) ! - on click will change the text to Unsafe! since it will use voulnerable function we added to safe window and will actually add malicious code to click event of the button. Click on it again and it will read the contents of the file.

To change the location of the injection files change line 2: var scripturl = 'http://localhost/' + scriptFile; to wherever you put the script files. To change what file is read from the local disk find c:\\\\temp\\\\security.txt in injection scripts and change them to whatever file you want to read.
Hopefuly this helps.

1 comment:

  1. Hi, Great.. Tutorial is just awesome..It is really helpful for a newbie like me.. I am a regular follower of your blog. Really very informative post you shared here. Kindly keep blogging. If anyone wants to become a Front end developer learn from JQuery Training in Chennai . or learn thru JQuery Training . or learn thru ES6 Online Training. Nowadays JavaScript has tons of job opportunities on various vertical industry.

    ReplyDelete