Server-side request forgery (SSRF) - Scala
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
Insecure Code Example
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.
Secure Code Example
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
Last updated
2023/09/18