Skip to main content

Server-side request forgery (SSRF)

Need

Prevention of unauthorized server-side requests to arbitrary domains

Context

  • Usage of Scala for building scalable and high-performance applications
  • Usage of play.api.mvc for handling HTTP requests and responses in Play Framework
  • Usage of play.api.libs.ws for making HTTP requests in a Play Framework application
  • Usage of global execution context for concurrent programming in Scala

Description

Non compliant code

import play.api.mvc._
import play.api.libs.ws._
import scala.concurrent.ExecutionContext.Implicits.global

class HomeController @Inject()(ws: WSClient) extends Controller {
def index(url: String) = Action.async { implicit request =>
ws.url(url).get().map { response =>
Ok(response.body)
}
}
}

The HomeController class has an index method that takes a URL as a parameter. This URL is supplied by the user and the server sends a GET request to this URL using the ws.url(url).get() method. The server then returns the response from the requested URL to the user.

This is an example of Server-side Request Forgery (SSRF) because the server is making a request to an arbitrary URL supplied by the user. The user can exploit this by making the server interact with internal resources that are not normally accessible. For example, a user could input a URL that points to an internal service that is only accessible from the server's network, potentially exposing sensitive information or functionality.

Steps

  • Validate the 'url' parameter to ensure it is a trusted and valid URL.
  • Implement a whitelist of allowed domains that the application can make requests to.
  • Sanitize user input to prevent any malicious input from being used in the URL.
  • Implement proper error handling and logging to detect and respond to any SSRF attempts.
  • Consider using a library or framework that provides built-in protection against SSRF, such as the Play WS library's built-in protection against SSRF.

Compliant code

import play.api.mvc._
import play.api.libs.ws._
import scala.concurrent.ExecutionContext.Implicits.global

class HomeController @Inject()(ws: WSClient) extends Controller {
private val allowedDomains = List("www.trusteddomain1.com", "www.trusteddomain2.com")

def index(url: String) = Action.async { implicit request =>
val domain = new java.net.URL(url).getHost
if (allowedDomains.contains(domain)) {
ws.url(url).get().map { response =>
Ok(response.body)
}
} else {
Future.successful(BadRequest("Invalid domain"))
}
}
}

The updated code includes a whitelist of allowed domains that the application can make requests to. This is done by creating a list of trusted domains (allowedDomains).

When a request is made, the domain of the provided URL is extracted using new java.net.URL(url).getHost. This domain is then checked against the allowedDomains list. If the domain is in the list, the request is made as before. If the domain is not in the list, a BadRequest response is returned, indicating that the provided domain is not allowed.

This approach prevents Server-Side Request Forgery (SSRF) by ensuring that the application can only make requests to trusted domains. It also provides a clear error message when an attempt is made to request a resource from an untrusted domain, which can be useful for logging and detecting malicious activity.

Remember to always keep the allowedDomains list updated with the domains that you trust and want your application to interact with.

References