There are many situations on the browser client when it would be desirable to make an AJAX call to a web-site or web service to fetch data. Many a time, the data source is on a different domain than the one from where the request is originating. These are called cross-domain requests. All browsers discourage scripts from making cross-domain requests as they are wary of the security implications in allowing arbitrary data requests across trust boundaries. At Scibler, the browser plugin that gets activated on the google mail domain needs to request data from Scibler servers to create an integrated experience for a user.
Partial mitigation of 2 important attacks is a reason why browsers advocate the Same Origin Policy (SOP) for data requests. One is called XSRF or CSRF (pronounced sea-surf) and the other XSS.
[XSRF] (http://en.wikipedia.org/wiki/Cross-site_request_forgery) stands for Cross-site Request Forgery, where in, trust on a user's browser that is providing a session with a particular site is hijacked by an illegitimate request on another site. For example,
- User is logged into his bank account.
- The bank stores the user's authentication tokens (more abstractly the trust) as cookies on the user's browser. This information acts like the session for the user with the bank.
- Every action on the bank's site inititated by the user submits these cookies to the bank for validation.
- The action should have side-effects otherwise it is not useful for the adversary.
- An adversary can build a web page with a carefully crafted request to the bank's site conducting an action (say transfer of money to his/her account)
- If the innocent user happens to execute this request when logged into his bank account, the action may go through as the cookies are submitted to the bank along with this request. The bank sees it as a legitimate transaction initiated by the user. The adversary does not see the response making the side-effect the target of exploitation.
XSRF attacks exploit the trust a site has on a user's browser.
[XSS] (http://en.wikipedia.org/wiki/Cross-site_scripting) stands for Cross-site Scripting, where, malicious payload is injected into a website, that exposes an XSS vulnerability (for example, allows DOM manipulation on user input or script injection), and reflects the same to other users. For example,
- A malicious user injects a dangerous script into a comment on a forum as an answer to a question.The forum site allows such injections and persists this in their database.
- Another benign user who visits the answer inadvertantly downloads the script onto his browser.
- The script exploits the benign user viewing the answer on the site.
The above example can be specialized as a persistent XSS attack, as the malicious script is persisted for other users. There is a non-persistent variant of XSS attack. For example, sites such as search engines display query parameters 'as is' on the page. These query parameters could be crafted to have attack payloads and an unassuming user clicking on such links could be attacked.
XSS is a complementary attack to XSRF. Here the user's trust on a site is being attacked.
The SOP that browsers enforce mitigate a few variants of XSRF and XSS attacks and definitely provide sandboxing of the DOM and cookies on the client. But it alone doesn't provide comprehensive protection. 'script' and 'img' tags are allowed to load cross-domain references as an exception.
Two popular methods to bypass SOP and make cross-domain AJAX calls are JSONP and CORS. At Scibler we have used both, JSONP initially before moving to CORS.
JSONP stands for JSON with Padding (confusingly so), though some people like to label 'P' for prefix. It makes use of the exception in SOP, allowing 'script' tags to load cross-domain resources, with some neat tricks to avoid evaluation. The trick used by JSONP is to invoke a locally (client) defined function. The data is wrapped as an argument in the invocation of this local function. At Scibler, we used JSONP initially to download our widget templates. Here is an example code of how we did it.
<script type="application/javascript"
src="https://scibler.com/widget/templates.json?OnJsonP=cacheTemplates">
</script>
The first step is to generate a script tag and inject it dynamically as shown above. This causes the browser to download the target of the src attribute. The target cannot be a JSON object, rather it needs to be any Javascript expression that uses local functions. In practice, the target is the data wrapped in a local function. In our case, the templates.json is as shown below.
templates.json:
cacheTemplates([ { "name" : "signin", "template" : "<div>...</div>" }, { "name" : "settings", "template" : "<div>...</div>"...])
A templates array is passed as an argument to the locally available cacheTemplates function in the templates.json. The payload is evaluated when downloaded on the client, which is nothing but calling cacheTemplates with the data as arguments. The cacheTemplates function which is locally present is given below.
function cacheTemplates(templates){
$.each(templates, function(index, template){
inject_file({id : template.name, type: 'text/template', element: 'script', code: template.template});
});
inited = true;
}
Frameworks like jQuery provide JSONP cross-domain functionality in the ajax function call. The code below shows its usage.
$.ajax({
url : scibler_domain + '/widget/templates.json',
dataType : 'jsonp',
jsonp : 'OnJsonPLoad'
});
CORS, expands to Cross-Origin Resource Sharing, is a newer and a more secure method for executing cross-domain requests. Legacy browsers may not support CORS unlike JSONP, but JSONP is inherently risky as it allows arbitrary scripts from other domains to execute in the current domains context and allows unprecedented access to cross-domain calls which could lead to XSRF kind of attacks. JSONP only works with GET requests, while CORS supports all HTTP verbs.
CORS is achieved by introducing HTTP header information to requests and validating these headers on the server side. A CORS-enabled browser sends Origin header information when making a cross-domain call. In our case, since google mail is making a cross-domain call to Scibler servers, the Origin header may look something like,
Origin: https://mail.google.com
The Scibler server responds with a another special header like the one below. This tells the browser whether to reject the cross-domain call or not. If the page origin and the server response don't match, the browser disallows the call.
Access-Control-Allow-Origin: https://mail.google.com
The server can also send a '*' as the header value, which means it allows pages from any origin to make calls.
At Scibler, our web servers set the CORS headers as shown below, allowing only cross-domain calls from google mail.
(ring.util.response/header response "Access-Control-Allow-Origin" "https://mail.google.com")
In summary, cross-domain calls are important in some scenarios but the SOP of browsers is a deterrent. There are multiple ways of by-passing SOP, CORS is a way of doing it on all modern browsers. JSONP with constraints can be used to support legacy browsers.