Introduction to Cross-Site Scripting (XSS)
Explore how cross-site scripting (XSS) attacks exploit web applications by injecting malicious JavaScript that executes in users' browsers, allowing harmful actions. Understand the mechanisms behind XSS, including script and DOM injection, and see real-world examples such as the Samy worm. This lesson helps you recognize how dynamic content can be exploited and prepares you for implementing defenses against these vulnerabilities.
We'll cover the following...
Introduction
We’ve seen the knock-knock joke principle applied to SQL (SQL injection). Let’s take a look at attacks using that same principle when applied to the HTML and JavaScript in a web page. We call this attack cross-site scripting (or XSS for short) if the attack injects JavaScript. We call it DOM injection if it injects regular HTML.
Let’s continue with the example from earlier in the chapter of a blogging site. One of the most basic requirements is for anyone using the site to be able to read posts written by other users. Suppose a reader writes a blog post such as this:
The reader would expect to be able to see this blog post in their browser. But what if instead of a heartwarming blog post like the one above, an attacker wrote this:
In a naive web application, the contents of this blog post would be concatenated directly into the HTML that makes up the page. So when another user loads this page, part of the HTML that will be loaded by the browser will include this script tag and the browser will dutifully execute this JavaScript.
This means that anyone who can author blog posts can author JavaScript that will execute in the browser of anyone else who visits the page. The example we’ve seen is harmless. But with a little imagination, we can think of more malicious payloads. Recall that JavaScript has the full ability to interact with all browser UI widgets such as buttons, links, text boxes, and radio buttons.
Batching a few of these interactions together means that JavaScript can be written to do anything that the logged-in user can do. This includes things like authoring new blog posts, changing the password of the logged-in user, deleting posts, adding comments to other posts—anything that a logged-in user can do.
Dynamic data can also be inserted into HTML element attributes like this:
<img src="picture.jpg" alt="This alt text is supplied by the user." />
Here we have an img tag with alt text that’s supplied by the user. The alt text could be supplied in the query string of the page that loads the img, or it could be read out of the database. In a naive web application, an alt text of this:
blah" onload="alert('Hello from alt text!');
would turn into this:
<img src="picture.jpg" alt="blah" onload="alert('Hello from alt text!');" />
Note that this payload contains the opening double quote for the onload attribute, not the closing one. It relies on the double quote that was intended to close the alt text attribute. This keeps the double quotes balanced and results in valid markup.
The most interesting thing about dynamic data in HTML attributes like alt is that it can lead to XSS without using < or > characters. This is another reason that the primary defense against XSS is HTML encoding, not stripping out suspicious characters.
To illustrate how this vulnerability can be exploited, let’s look at what would happen with alt text like this:
blah" onload="document.getElementById('submitbutton').click();
If that were loaded naively into the alt text above, we’d have HTML like this:
<img src="picture.jpg" alt="blah"
onload="document.getElementById('submitbutton').click();" />
If this were placed into a page with a button with the ID submitbutton, then this JavaScript will click that button when the image loads. From here, you can see how this approach could be extended to script arbitrary interactions with a web page.
Case study: Samy worm
For an interesting case study of what XSS can do, consider the case of the Samy worm. Samy Kamkar, the author of the worm, introduced a little bit of JavaScript onto his home page on Myspace. When a logged-in victim visited Samy’s page, Samy’s JavaScript would execute in the victim’s browser. This JavaScript would programmatically click all the buttons that were required to add Samy as a friend and copy itself onto the victim’s home page. Then, when yet another victim visited the first victim’s home page, they too would add Samy as a friend and copy the JavaScript onto their home page. This worm quickly went viral and in less than a day, more than one million friends had been added to Samy’s account.
The beauty of the XSS attack is all the malicious code executes in the victim’s web browser. Every click and keypress originate from the victim’s machine, so network logs and access logs all show traffic from the victim’s logged-in machine.
Try out an XSS attack!
var qs = require('querystring')
var url = require('url')
var fs = require('fs')
require('http').createServer((req, res) => {
let query = qs.parse(url.parse(req.url).query)
res.writeHead(200)
let body = query.body || ''
res.end(fs.readFileSync(__dirname + '/index.html').toString().replace('__RESULTS__', body))
}).listen(7888)The ‘website’ above takes blog posts as input and prints them.
Try entering <script> alert("xss attack!")</script> into the field above! You’ll see how it’ll get executed.
In the next lesson, we’ll study how to lay down some foundation in HTML that will come in handy when drafting defenses.