A Kotlin web application that is vulnerable to cross-site scripting, or XSS, has weak points through which users can inject JavaScript code. Such weak points exist in web applications that accept user input. For example, you can find these kinds of weak points in online forums that allow users to post comments.
XSS is a common web application security vulnerability. It can lead to some serious issues like the exposure of sensitive user data stored in cookies.
In this post, we'll take a look at some examples of Kotlin XSS attacks. In addition, we'll look at how to prevent XSS attacks in Kotlin applications.
Before we continue, let's take a closer look at what XSS is.
What Is XSS?
XSS stands for cross-site scripting. It's a kind of web application security vulnerability that lets an attacker inject malicious JavaScript code into a website. The code is then executed when users load the affected pages on a web browser.
For example, the following URL accepts user input for the value of a search query:
https://example.com/search?query=hot+dog
Here, the user is searching for "hot dog". The search results page also displays a message with the search query. The following Kotlin code handles the display of the message:
get("/search") {
val query = call.request.queryParameters["query"]
call.respondText("<p>Showing results for $query</p>", ContentType.Text.Html)
}
This code simply reads the value for the search query from the URL and displays it in the response HTML. However, the code is vulnerable to XSS attacks. An attacker can inject JavaScript to the page by manipulating the URL to the following:
https://example.com/search?query=<script>alert("Hehe!")</script>
This new URL will cause the search results page to show a JavaScript alert with the message "Hehe!".
With that, an attacker was able to inject JavaScript code into the search results page. Although the script only causes the page to show an alert, it is possible to run more malicious JavaScript code using the same trick. To learn more about XSS in general, check out our detailed blog post on the topic here.
Now that you have a better understanding of what XSS is, let's take a look at some examples of XSS in a Kotlin application.
Note: For all our examples, we'll be referring to a Kotlin application powered by Ktor.
Ktor is a Kotlin framework for building web applications and HTTP services. It provides an easy way to write and run code during the development phase using IntelliJ IDEA.
1. Rendering Data From URL Query String
A query string is the part of a link or URL that follows the first ? sign. For example, in the link https://www.google.com/search?q=ktor&oq=ktor, the query string is included in ?q=ktor&oq=ktor. Also, the query string for the URL contains two parameters, q and oq, with both parameters having their values set to ktor.
A query string is very important in GET requests. For example, it makes sharing links to a specific part of a website possible. However, it can also enable XSS attacks. In this example, we'll see how displaying data from a query parameter can lead to an XSS attack.
The following is a URL to a specific item on an e-commerce website:
https://example.com/store?item=rice+cooker
The web page displays the value for item in its HTML. As a result, an attacker may generate a malicious version of the URL and send it to an unsuspecting victim.
https://example.com/store?item=<script>window.location="https://notsafewebsite.com"</script>
This version of the URL will redirect the user from the e-commerce website to notsafewebsite.com thanks to the JavaScript code that has been injected.
Prevention
In order to prevent this type of XSS attack, you should avoid rendering values from query strings without proper sanitization. With the help of sanitization, you can check and filter user input before using them in your code.
Here is a small piece of code that can prevent the attack in this example.
get("/search") {
val query = call.request.queryParameters["query"]
val regEx = Regex("[^A-Za-z0-9 ]")
val searchKeyWord = regEx.replace(query!!, "")
call.respondText("<p>Showing results for $searchKeyWord</p>", ContentType.Text.Html)
}
This code uses a regular expression to check if the search query contains only alphanumeric values. Then it removes any special characters present in the value.
2. Persistent User-Generated Content
Here the attacker injects malicious JavaScript code via a feature like a comment box on a blog. The code is stored in a database like MySQL and runs every time a user loads the page with the comment.
So let's consider an example blog post that a Kotlin back-end application powers via https://example.com/blog?post=112. Then a malicious user drops a comment with the following post request:
body: {
"post": 112,
"comment_by": "John Doe",
"message": "<script>alert("Hehe!! You have been hacked!!!")</script>"
}
This request will save the value for message to the database using 112 as a key for connecting the comment to a blog post. Once any user visits the page for blog post 112, the comment is loaded. As a result, the JavaScript code is also executed.
With this type of XSS attack, an attacker can target multiple users with a single comment. Since the malicious code is stored in a database, they only have to write it once for it to be executed multiple times for all users.
Prevention
In order to prevent this type of XSS attack, you should validate user input before storing it in the database. As a second layer of security, you should also sanitize user input after retrieving it from the database. Doing so will help prevent an attack should an attacker manage to bypass the initial validation.
Another effective way to bypass this type of attack is to use a templating plugin like FreeMarker to render HTML in a Kotlin application. FreeMarker is a template engine for Java. It makes it easy to build dynamic HTML by allowing seamless inclusion of Kotlin variables and expressions in HTML code. Here is a small piece of FreeMarker HTML code:
<body>
<p>Showing results for ${query}</p>
</body>
The final fix we'll be considering for this example is the use of Ktor's respondText() method without setting the content type parameter to HTML. This will cause Ktor to render all content of a string as plain text. Take a look at the following string:
val message = <strong>Hello world! Nice to meet you!!</strong>
It will render the following output:
<strong>Hello world! Nice to meet you!!</strong>
Attempting to display the same string with content type set to HTML will display a bold text that reads Hello world! Nice to meet you!!
Summing Up
XSS attacks are common. You should always test your applications for XSS before pushing them to production.
From our two examples in this post, a hacker can target users of your application using two methods. The first requires the attacker to inject malicious JavaScript code using a URL. Then the attacker can share the dangerous link via email or on social media for unsuspecting users to click on. In the second example, the data persistence capability of a web application is the weak point. As a result, all users who visit a page that loads the malicious code from the database will be affected.
Validation and sanitization of user input can reduce the risk of attacks in both examples. In addition, using a template engine can go a long way in preventing attacks. Never trust users to provide valid inputs all the time. Some users may supply values that will break your application intentionally.
This post was written by Pius Aboyi. Pius is a mobile and web developer with over 4 years of experience building for the Android platform. He writes code in Java, Kotlin, and PHP. He loves writing about tech and creating how-to tutorials for developers.