Jason Sultana

Follow this space for writings (and ramblings) about interesting things related to software development.

Cross Site Scripting (XSS)

06 May 2024 » security, php

G’day guys!

Continuing our coverage of various security vulnerabilities, today I wanted to review Cross Site Scripting, commonly abbreviated to XSS. While technically speaking there are probably a few different flavours of this, I’m going to focus on two types of XSS; stored and unstored.

Stored XSS

Consider a social networking app where a user can make a post, and this post will appear in the timeline of all of their contacts. The backend logic for displaying posts might look something like:

    $posts = get_posts_for_user($user_id);

    foreach ($posts as $post) {
?>
    <div class="post">
        <p class="post-username"><?php echo $post->username; ?></p>
        <p class="post-body"><?php echo $post->body; ?></p>
    </div>
<?php
    }

It’s very contrived, but hopefully you get the idea. The server-side code retrieves all of the relevant posts for the logged in user, loops over them and lists them one by one.

Now consider what would happen if one of the user’s contacts submitted a post with the following content:

Feeling hacky today!

<script>
    //
</script>

Whenever this post gets rendered by another user’s browser, the script tag will execute. I’ve left this blank for brevity, but a couple of examples of what it might do are:

  • Download a malicious file to the user’s computer
  • Change the locally displayed content of other posts, or perhaps a post made by a specific person
  • Deface the social media website
  • Play some hidden media that the user can’t turn off

Because the post got saved (aka stored) into the social networking application’s database, I’d classify this as a Stored XSS attack.

Unstored XSS

Consider an online store that allows users to search for products. The search term is stored in the query string, so an example url might look like https://www.onlinestore.com/search?q=Cool+products.

The server side code for the search page might look something like:

<h3>Search results for <?php echo $_GET['q']; ?></h3>
$results = do_search$($_GET['q']);

foreach ($results as $result) {
    // ...
}

Given this, imagine that an attacker sent an email or other message to a targeted user including the following link:

<a href="https://www.onlinestore.com/search?q=<script></script>">Products on sale</a>

Once again, I’ve left the script tag empty for brevity, but we can imagine all sorts of things that an attacker might try here, like:

  • Changing the html of the page to include or modify the product list
  • Changing the links of the products to point to a phishing website
  • Generally defacing the online store

Because the malicious content comes from the query string instead of a database record and is therefore not ‘stored’, I’d classify it under the unstored category.

What exactly is ‘Cross Site’ about XSS?

With these two examples, you might actually be looking a bit like Obi Wan here and wondering where ‘Cross Site Scripting’ got its name from, since there doesn’t seem to be anything inherently ‘Cross Site’ about the vulnerability. According to Wikipedia, XSS got its name since in the early days of the exploit being used, it was typically done by one website making a call to another. This is a bit of a misnomer though, since there doesn’t need to be a second website to take advantage of this vulnerability.

Prevention

While the appropriate prevention will likely depend on context, I’d recommend starting with the following 2 options:

  1. Always encode non-trusted input if it must be displayed, such that <script> will be encoded to &lt;script&gt; and simply displayed instead of executed as a script tag.

  2. If the user-submitted input must be displayed verbatim (eg: a forum that allows users to style their posts), consider using an HTML Sanitisation tool that supports a whitelist of allowed elements and attributes. User-submitted input that violates the configuration of the HTML Sanitiser should be rejected.

One thing that I wouldn’t recommend is trying to pre-emptively sanitize all incoming requests like I mentioned in Preventing XSS in .NET Core WebAPI. In practice, I’ve found this to not be feasible and that simply encoding data on the way out tends to be the better approach.

<script> document.write(‘Damn’); </script>

And that about wraps me up! Have you guys ever run into an XSS vulnerability out there in the wild, or do you have any other tools for preventing XSS that I haven’t mentioned? Let me know in the comments.

Catch ya!