“AJAX is for asynchronous calls within same origin whereas HTML5 CORS is for asynchronous calls across origins”. This is a popular comparison of AJAX vs CORS which many web developers do, but there is a lot beyond this!
Similar to the above response header, there are a bunch of response headers which help browsers in checking various access permissions. If you are new to CORS, I suggest you to visit HTML5 rocks, which has a detailed tutorial. In this post, I would like to focus on browser security checks.
Browser security checks – AJAX vs CORS:
Let us say you are using a browser which does not support HTML5 CORS and your application is hosted at http://localhost:81. If you open http://localhost and try to make an AJAX call to your app, you will get this security error message:
"Access to restricted URI denied" (message text varies across browsers, essentially conveying SOP violation).
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost:81/handler.ashx", true);
var params = 'name=AJAX';
Browsers do this Same Origin Policy check even before they trigger a network call.
Now if you install a browser which supports CORS and try the same (without configuring any response headers), you will get the message:
“XMLHttpRequest cannot load http://localhost:81/. Origin http://localhost is not allowed by Access-Control-Allow-Origin”.
The message clearly conveys that the check happened after making a call to the remote server. This is obvious, since CORS is based on response headers and browsers do not have any clue whether the remote server allowed the call or not without checking the response. In this context, browser developer tools sort of confuse developers by saying that “the request is cancelled”. In reality, browsers “do the request but just do not render the response” in case CORS headers are not found. To test this, you can simply log the call on your server and you will have log entries.
<%@ WebHandler Language="VB" Class="Handler" %>
Public Class Handler : Implements IHttpHandler
Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
Dim str As String = context.Request.Params("name")
Dim sw As StreamWriter
Dim strDate As String
strDate = context.Timestamp
sw = New StreamWriter(context.Server.MapPath("logs.txt"), True)
sw.WriteLine(strDate + " : " + str)
context.Response.ContentType = "text/plain"
context.Response.Write("Hello " + str)
Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
The above code is the content of my remote .ashx handler hosted on http://localhost:81 (okay, I’ve written the above code in VB but who cares! As long as you understand what it does, language doesn’t matter. My next demos will be using Java Servlets/PHP..yes, I’m coding in these recently..yay!)
So web developers who have used AJAX and are moving towards HTML5 should know this subtle but important difference.
Cross origin form submission now AJAXified in CORS!
Not sure if you have observed or not, the first code snippet in this post uses a content-type “application/x-www-form-urlencoded”. If you haven’t figured it out, there is a more familiar version of it which you already know:
<form method="post" action="http://localhost:81/handler.ashx">
<input type="text" name="name" value="fake form">
<input type="submit" value="Submit">
When HTML forms are submitted, they use “application/x-www-form-urlencoded” as the default content type. Since forms anyways do cross origin posts inherently, this “feature” was also included in HTML5 CORS specifications. This is the reason why we were able to log the CORS message in the second snippet. So all the three content types which are supported by HTML forms (“application/x-www-form-urlencoded”, “multipart/form-data”, “text/plain”) can be used in CORS to do a cross origin request (Don't you smell attack scenarios here? Stealth mode attacks? Yeah!)
No preflight request for CORS POST request?
The HTTP methods GET and HEAD are called “safe methods” since they should not cause any state changes on the server. POST requests are meant to change the state on the server and can be misused. So there should have been a preflight request for the above CORS POST request, but browsers did not do such thing in the above demo.
The reason for this boils down to what is allowed vs not allowed prior to HTML5 specs. In HTML4, we could do cross origin requests using <img src=’…’>, <script src=’…’>, <form method=”GET/POST” action=’…’> etc. Following the same rules, cross origin GET requests and some POST requests can still be done using CORS. So what are those “some” POST requests? These are nothing but what we have seen above in the form submission case. i.e., POST requests with various form content-types. Other than these, any sort of POST request will require a preflight request, which is the security enhancement in CORS.
HTML Forms and CSRF attacks go hand in hand. What about CORS & CSRF?
Yes, CSRF attacks using HTML forms are quite popular and one of the well known defenses is sending random, unique nonce in every request. Since CORS POST requests mimic HTML form requests, they can be victims of CSRF attacks and hence every CORS POST request should use CSRF defenses. If this is not considered seriously, it paves way to a number of stealth attacks such as silent file uploads. In the first snippet, if you set “xhr.withCredentials=true”, even cookies can be sent in the POST requests and hence replay attacks can be done.
Winding up the show, CORS is beautiful and enhances the modern web interactions in many ways. We already know how bad the state of AJAX security has been, owing to poorly written code. If CORS is studied as ‘yet another new thing on the web’, without understanding the internal details, web applications will suffer big time leading to serious attacks. Time to focus on the security aspects of modern web specifications! No escape!!
Update: You may check the source code of the demo used in the article here: https://github.com/novogeek/AJAX-vs-CORS