Skip to main content

Stored cross-site scripting (XSS)

Need

Mitigation of stored cross-site scripting (XSS) attacks

Context

  • Usage of Scala for building scalable and functional applications
  • Usage of play.api.mvc for handling HTTP requests and responses in Play Framework
  • Usage of play.api.data for handling form data in Play Framework
  • Usage of play.api.data.Forms for handling form data in Play Framework
  • Usage of views.html.index for rendering HTML templates in a Node.js application

Description

Non compliant code

import play.api.mvc._
import play.api.data._
import play.api.data.Forms._

case class UserInput(data: String)

val userInputForm = Form(
mapping(
"data" -> text
)(UserInput.apply)(UserInput.unapply)
)

def saveData = Action { implicit request =>
val userInput = userInputForm.bindFromRequest.get
Ok(views.html.index(userInput))
}

The above Scala code is a simple example of a Play Framework controller that is vulnerable to Stored Cross-Site Scripting (XSS) attacks.

In this code, the UserInput case class is used to capture user input from a form submission. The userInputForm variable is a Play Framework form mapping that binds the incoming request data to an instance of UserInput.

The saveData method is the action that handles form submissions. It binds the incoming request data to the UserInput form mapping, effectively capturing the user's input. The user's input is then directly passed to a view template (views.html.index(userInput)) without any form of sanitization or encoding.

This means that an attacker can submit arbitrary JavaScript code through the form, which will then be stored and displayed in the view template every time it is rendered. This is a classic example of a Stored XSS vulnerability because the malicious script is permanently stored by the server, and served to users who view the affected page.

Steps

  • Sanitize user input: Implement input validation and sanitization to prevent the execution of malicious scripts.
  • Use appropriate encoding: Encode user input before displaying it in the HTML response to prevent the interpretation of HTML tags and JavaScript code.
  • Implement Content Security Policy (CSP): Configure a Content Security Policy to restrict the types of content that can be loaded by the application, including scripts.
  • Enable XSS protection mechanisms: Enable built-in XSS protection mechanisms provided by the framework or use third-party libraries to automatically sanitize user input.
  • Educate developers: Provide training and guidelines to developers on secure coding practices, including input validation and output encoding.

Compliant code

import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
import org.apache.commons.text.StringEscapeUtils

case class UserInput(data: String)

val userInputForm = Form(
mapping(
"data" -> text
)(UserInput.apply)(UserInput.unapply)
)

def saveData = Action { implicit request =>
val userInput = userInputForm.bindFromRequest.get
val sanitizedInput = StringEscapeUtils.escapeHtml4(userInput.data)
Ok(views.html.index(UserInput(sanitizedInput)))
}

The updated code includes the use of the StringEscapeUtils.escapeHtml4 method from the Apache Commons Text library to sanitize the user input. This method escapes any special characters in the input string that could be interpreted as HTML tags or JavaScript code, effectively preventing stored cross-site scripting (XSS) attacks.

Here's a breakdown of the changes:

  1. Import the Apache Commons Text library: This library provides utilities for manipulating and interpreting text, including the StringEscapeUtils.escapeHtml4 method used for input sanitization.

    import org.apache.commons.text.StringEscapeUtils
  2. Sanitize user input: Before passing the user input to the view, it is sanitized using the StringEscapeUtils.escapeHtml4 method. This method escapes any special characters in the input string that could be interpreted as HTML tags or JavaScript code.

    val sanitizedInput = StringEscapeUtils.escapeHtml4(userInput.data)
  3. Pass sanitized input to the view: The sanitized input is then passed to the view instead of the raw user input.

    Ok(views.html.index(UserInput(sanitizedInput)))

By sanitizing user input in this way, the application can effectively prevent stored XSS attacks. However, it's important to note that this is just one layer of defense and should be used in conjunction with other security measures, such as implementing a Content Security Policy (CSP), enabling XSS protection mechanisms, and educating developers on secure coding practices.

References