Understanding Cross-Site Scripting (XSS): A Simple Guide

Introduction to Cross-Site Scripting (XSS)

Cross-Site Scripting, commonly known as XSS occurs when an attacker utilizes a web application to send harmful code, typically in the form of a browser-side script, to a different end user. In simple words,  the danger of XSS arises when a web application incorporates user input into its output without proper validation or encoding, creating an opportunity for an attacker to execute harmful scripts.

How XSS Works

The mechanism behind XSS is relatively straightforward but dangerous. It exploits the trust a user has in a particular website. When a malicious script is sent via an XSS attack, the user’s browser has no way to discern that the script is not to be trusted and executes it. Since the script appears to originate from a trusted source, it can access sensitive data like cookies, session tokens, or other critical information stored by the browser.

Types of XSS Attacks

Cross-Site Scripting (XSS) attacks are a prevalent issue in web security. They exploit vulnerabilities in web applications that fail to adequately sanitize user input. Let’s delve into each type you’ve mentioned, offering more details and examples.

1. Reflected XSS Attacks

Reflected XSS attacks occur when a web application or API sends user-supplied data back to the browser without proper validation or escaping. This vulnerability is often found in search engines, forms, and other mechanisms that reflect user input as part of their immediate response.

Imagine a search function on a website that directly includes user input in its response. An attacker could craft a URL with a search term that includes a malicious script. When a user clicks on this link, the script executes in their browser. For instance, the URL might be http://example.com/search?q=<script>alert('XSS')</script>. If the search term is reflected as-is in the search result page, it would execute the script.

2. Stored XSS Attacks

Stored XSS attacks occur when the malicious script is permanently stored on the target system, such as in a database, message board, comment field, or any other location where data is stored. Most application sees data as it is (regardless of whether they are malicious), and therefore will simply store the payloads.  Each time users access this data, the script gets executed in their browser.

Consider a forum where users can post messages. An attacker posts a message containing a script, such as <script>fetch('/steal-cookie').then(response => document.write(response))</script>. Every user who views this message will execute the script, potentially leading to cookie theft or other malicious actions.

3. DOM-based XSS

In DOM-based XSS, the attack payload is executed as a result of modifying the Document Object Model (DOM) of the web page in the client side, often in response to some user action like clicking a link or submitting a form. This type of attack occurs entirely in the browser and does not involve server-side processing of the malicious data.

Consider a web application that uses JavaScript to process and display URL parameters. An attacker could manipulate the URL to include a script, like http://example.com/page.html#<script>alert('XSS')</script>. If the JavaScript code naively uses the fragment of the URL to modify the DOM, it could execute the script.

4. Blind XSS

In blind XSS attacks, the malicious script is stored and executed outside the immediate web application context. It often targets administrators, moderators, or support staff who interact with the data through a different interface, like a backend CMS or support tool.

An attacker submits a support ticket with a script embedded in it. This script remains dormant until a support staff views the ticket in their internal support system. When viewed, the script executes in the context of the staff’s session, potentially leading to account compromise or data leakage.

5. Alternate XSS Syntax

Attackers often use creative syntax to bypass filters and execute scripts. This can include using non-standard tags, event handlers, or exploiting browser-specific behaviors.

Instead of the classic <script> tag, an attacker might use an image tag with a JavaScript event handler, like <img src=x onerror=alert('XSS')>. When the browser attempts to load the image and fails (since ‘x’ is not a valid image), the onerror event triggers the execution of the script.

Consequences of XSS Attacks

The impact of XSS attacks can range from mere annoyances to severe security breaches. The most serious consequences involve hijacking a user’s session, allowing attackers to impersonate the user and gain access to sensitive data or functionalities. XSS can also lead to content spoofing, where attackers manipulate the content on a website, and in extreme cases, deliver malware or perform other harmful operations.

Here are some real life examples of such attacks (via bug bounty):

  1. In GitLab 15.0.0, a XSS vulnerability was discovered in the Customer Relations feature, where injecting a script into a contact’s name fields triggers JavaScript execution when using quick commands like /add_contacts or /remove_contacts. [link]
  2. An attacker can exploit a vulnerability in Uber’s readme.io documentation pages to perform a stored XSS attack, potentially hijacking the accounts of developers viewing the affected pages. [link]
  3. Shopify rich text editors allowed data: URLs as image sources, enabling stored XSS via SVG images in product descriptions, potentially compromising the /admin area with little user interaction. [link]

Identifying XSS Vulnerabilities

1. Testing for Reflected XSS

  • Input Validation: Experiment with various inputs in query parameters. Include typical XSS payloads like <script>alert('XSS')</script>. If the script executes, it’s a clear sign of vulnerability.
  • Observe Responses: Pay attention to how the application responds to your inputs. Does it filter or escape special characters? If not, it might be susceptible to XSS.
  • Use Browser Developer Tools: These tools can help you see if your input is reflected in the page’s source without proper sanitization.

2. Testing for Stored XSS

  • Persistent Input Testing: Submit scripts through forms or input fields that store data, such as comment sections or user profiles. Later, check if these scripts are executed for anyone viewing the stored data.
  • Data Handling: Analyze how the application handles and stores user inputs. Are inputs sanitized before storage? If stored data is displayed without proper encoding, it could lead to Stored XSS.
  • Test Across User Accounts: Use different accounts to test if a script injected by one user affects other users, indicating a stored XSS issue.

3. Testing for DOM-based XSS

  • Client-Side Code Review: Scrutinize the JavaScript or client-side code. Look for instances where user input is directly included in the DOM.
  • Dynamic Testing: Modify DOM elements on the fly using tools like browser consoles to see how the application reacts to changes in the DOM.
  • Trace Data Flow: Track how data flows from sources (like URL parameters) to sinks (where data is output in the DOM). Unsafe data handling here can lead to DOM-based XSS.

General Tips

  • Use Automated Tools: While tools like Nessus and Nikto are helpful, they might not catch everything. Use them as a starting point but don’t rely solely on them.
  • Manual Code Review: Complement automated tools with a manual review of the code, particularly focusing on how user inputs are handled.
  • Payload Variations: Use a variety of XSS payloads. Some applications may filter basic scripts but fail against more sophisticated or encoded payloads.
  • Consider Context: The effectiveness of XSS payloads can depend on where in the HTML or JavaScript they are injected. Context matters a lot in XSS exploitation.
  • Testing Environment: Always test in a safe and legal environment, preferably a test instance of the application you have permission to test.

Prevention and Mitigation Strategies

1. Input Validation and Sanitization

  • Principle: Do not trust any user input. Validate it against a set of rules (like type, format, length) and sanitize it to remove harmful elements.
  • Example Code:
    const express = require('express');
    const xss = require('xss');
    
    const app = express();
    app.use(express.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded
    
    app.post('/submit', (req, res) => {
        // Sanitize the user input
        const sanitizedInput = xss(req.body.userInput);
    
        // Process the sanitized input (e.g., store in a database, display back to the user, etc.)
        // For demonstration purposes, we'll just send it back as a response
        res.send(`Sanitized Input: ${sanitizedInput}`);
    });
    
    const PORT = 3000;
    app.listen(PORT, () => {
        console.log(`Server running on port ${PORT}`);
    });
    

2. Output Encoding

  • Principle: When displaying user input, encode it to prevent it from being executed as part of the webpage.
  • Example Code:
    <?php
    $userInput = "<script>alert('XSS Attempt');</script>";
    
    // Encoding the user input before outputting it
    $safeOutput = htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
    
    // Outputting the encoded data
    echo "Safe Output: " . $safeOutput;
    ?>
    

3. Using Security Headers

  • Principle: Implement HTTP headers to instruct the browser on how to safely handle the content.
  • Implementation:
    • Content-Type with charset defined ensures the browser interprets the content in the intended format.
    • X-Content-Type-Options: nosniff prevents the browser from MIME-sniffing a response away from the declared content-type.

    This can be implemented in web server configurations or via server-side scripting.

4. Content Security Policy (CSP)

  • Principle: CSP allows you to specify which resources can be executed or loaded on the webpage.
  • Example:
    • A basic CSP header: Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-source.com;
    • This CSP only allows scripts to be loaded from the same origin and a trusted source.

5. OWASP XSS Prevention Cheat Sheet

  • Overview: This is a comprehensive guide covering various XSS prevention techniques. It includes details on properly handling data in HTML, JavaScript, CSS, and URL contexts.
  • Application: The cheat sheet provides specific code examples and best practices, like using appropriate escaping/encoding libraries and techniques depending on the context in which data is output.

Additional Best Practices

  • Use Frameworks that Automatically Escape XSS: Many modern web frameworks have built-in mechanisms to prevent XSS. For instance, React automatically escapes content unless explicitly told not to.
  • Regular Security Audits: Regularly review and update your practices to combat new XSS techniques.
  • Educate Developers: Ensure that your team understands the importance of these security measures and how to implement them correctly.

Conclusion

XSS poses a significant threat to web security, capable of compromising user data and undermining the integrity of web applications. Understanding its mechanisms, types, and consequences is crucial.

Equally important is the implementation of effective prevention strategies, such as input validation, output encoding, and adhering to security best practices. Regular security reviews and testing play a vital role in identifying and mitigating XSS vulnerabilities. By adopting a proactive and informed approach to web security, organizations can significantly reduce the risk of XSS attacks.

Static Web Page: Using Hugo, Gitlab, CloudFlare and Forestry.io

A static web page is often HTML file that is served as it. It means that whatever is uploaded is displayed. No logic or any other server-side codes.

Those who have been following me know that I changed my blogging platform a couple of times. I’ve used Tumblr, Ghost, and just before migrating, WordPress. I’m someone who hates change but ironically every time I changed or moved to a new platform something was missing.

Tumblr was free(and had support for custom domains) but had tons of additional analytic scripts which makes your site clunky.

Ghost was great, lightweight, had an excellent editor, and great in-built seo, however, the hosting was expensive at $19/month, and as a student that wasn’t feasible. I self-hosted ghost using this script but whenever there is a new update, I’ll have to go through the process of backing up my blog, wiping my VPS, and reinstalling the platform using the script.

WordPress is still considered to be the best blogging platform for those who are starting out. It has an amazing plugin system that takes care of most of a blogger’s concerns (security & SEO), great security(subjective), and a supportive community. The problem was still the same, try finding cheap hosting for WordPress. Even shared hosting isn’t sustainable since this blog isn’t monetized (and I’m still a student).

I’ve literally run out of a good blogging platform that caters to my need.

Considerations

Before this migration, I had performed some serious research and decided that the current setup was best for me.

Here are some of the considerations:

  • Cost
  • Speed
  • Workflow
  • Future-proof

Cost

I’m a student, I’m an Asian. I need free stuff.

Hosting a VPS on a ram node was honestly affordable. I was able to use ghost comfortably until the update rolled in (they are currently working on a CLI update feature).

Hosting WordPress is a HARDCORE business. I mean seriously. Google “WordPress hosting” and you will be flooded with options with different reviews. My previous host was Siteground and I have no complaints with their service. This blog hosted on it was fast, the user interface was simple and navigable, and overall a great host. The cheapest/smallest plan cost $3.95/month(annual subscription) for the initial purchase; on renewal, it jumps to $9.95. That’s a 2.5x multiplier! It’s only worth it if you manage to monetize your site within that one year.

At this point, the only thing that I want to be paying for is my domain name.

Speed

Who wants to visit a slow site?

There are many factors that affect a site’s speed. I’ll be eliminating them by shifting them to a static site. Previously, I had no problems with my website and I intend to keep it that way.

I made a review on Cloudflare a while back and it still stands true that they still provide amazing services for free. Yes, you heard it. HTTPS/2, CDN caching, asynchronous javascript, and stunning analytics all for free(this is not an ad).

I’ve consistently maintained a relatively fast site using Cloudflare, and until their free tier caching deteriorates, I will continue using it.

Good job Cloudflare.

Workflow

All I’m doing is writing stuff and getting my thoughts out there. The traditional blogging platform is pretty straightforward, create a new post using the GUI and click on publish to get your words out.

However, I’m using a Hugo, a static site generator, the usual workflow using a static site generator would involve using the terminal and a plain boring text editor. Yuck.

I’ve decided to use Gitlab pages with Hugo, and Forestry.io for editing.

Gitlab pages is similar to GitHub pages, the former is my choice as they offer unlimited private repository.

As for editing, Forestry.io is compatible with both Gitlab and Github In addition, they recently received some funding (suggesting further development). And most importantly, their support even for the free tier is superb.

Future-proofing

I’ve done two major migrating, Tumblr → Ghost, Ghost → WordPress. It was time-consuming and outright unproductive. In the future I might go back to Ghost, hence the future-proofing. I need to be working with a platform that supports markdown.

My setup is independent of each other and I’m able to swap them out anytime without affecting the setup.

Hosting DNS CDN Platform
Gitlab Cloudflare Cloudflare Hugo

Migrating to Hugo

The migration to Hugo was a pleasant one, using this free WordPress plugin that does a decent job.

We first have to install Hugo, and I must say they have amazing documentation on how to get it done. My Hugo setup was working in literally minutes.

It’s similar to installing Python on windows.

  1. Download the executable
  2. Add a path to the executable in the environment

Upon installing try calling help to see if it’s correctly installed.

hugo help

Once Gitlab is correctly installed you can create a new site using:

hugo new site blog

I used this video as a quick start-up. A word of advice, when using a static site generator remember that it is a generator and you are passing the configuration to Hugo(in this case) to generate the site to your liking.

Uploading to Gitlab

Create a new repository by heading over to https://gitlab.com and logging in.

Click on the + sign and click “New Project”

Set the global variables of git

git config --global user.name "Edric"
git config --global user.email "[redacted]"

Clone the blog repository that we just created locally

git clone https://gitlab.com/[user]/blogexample.git

Play around with Hugo and make sure that the generated site is what you expect because when we upload these files and config to Gitlab, they will be the ones building your static site.

Serve the static site locally using:

hugo server

Now, push the project back to Gitlab

git add --all
git commit -m "initial upload"
git push origin master

Almost immediately after you push the files, Gitlab’s pipeline will start building your site. Pay attention to the errors and fix them accordingly. I spent more time than I should have here simply because I was going after the spray and pray methodology instead of reading the error.

Set up Cloudflare

Gitlab made a very detailed article on doing so https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/. Pay special attention to Step 4 in the guide.

Forestry.io

Forestry.io is incredibly easy to use. Just head over to https://forestry.io/docs/getting-started for a tour, and if you require any additional help (I doubt so), chat with them using the chat feature.

Minor touch up

Bypass IP address filter using SSH forwarding

Update 04 Dec 2016: I no longer use DigitalOcean. I figured that RamNode is cheaper and suits me more.

A couple of weeks ago, a friend of mine asked if the process of setting up a VPN was difficult.

He needed it to bypass the ISP’s IP filter to access a few sites from the United States.

Well, getting a VPN is one of the options but it isn’t the only way. Since all he needed was to browse the filtered website, SSH forwarding is probably a faster and cheaper option.

Here is an overview:

  1. Get a virtual private server (VPS)
  2. Download Bitvise and SSH to your VPS
  3. Change your browser proxy settings

1. Getting a VPS

There are many VPS providers however, my personal favorite is RamNode since they offer a range of subscriptions which caters to your need.

Create an account and go to Services > Order New Services.

Choose the type of VPS that suits you. This blog used to be hosted on OpenVZ SSD VPS (SVZS).

Now select your country.

Select how much RAM is required.

Fill in your billing cycle, hostname and operating system.

Check out and you are good to go. Now we will head over to using the SSH client.

2. Downloading and SSH to VPS

Enter your IP address and password.

Navigate to Services tab end ensure that SOCKS/HTTP Proxy Forwarding is enabled.

Click Login and you will be prompted to change your root password.

3. Change browser proxy

On Google Chrome, download Proxy SwitchySharp. This is a chrome extension that allows you to switch your proxy settings quickly.

Enter the settings of the extension and set the value to the follow:

Profile Name: droplet-1
HTTP Proxy: 127.0.0.1
Port: 1080

Check the option Use the same proxy server for all protocols

When you click on your extension icon, you should see the new profile.

Click on the new profile and give it a few seconds. To check if everything is working, head over to google and search “what is my ip”.

You should see that your IP is the IP of your VPS’s address.

To check if everything is working, head over to google and search “what is my ip”.

You should see that your IP is the IP of your VPS’s address.

To be extra sure, you can sniff the traffic with WireShark and see that the packets are sent under the SSH protocol.

Now you can visit any sites using your VPS’s IP address, essentially bypassing the IP filter.

Thanks to Siyavash who taught me this neat trick during my internship.