Time-based SQL Injection - Scala

Time-based SQL Injection - Scala

Need

Prevention of time-based SQL injection attacks

Context

  • Usage of Scala for building scalable and functional applications
  • Usage of DatabaseConfigProvider in Play Framework with Slick for database configuration and access
  • Usage of slick.jdbc.JdbcProfile for database access and query execution
  • Usage of javax.inject.Inject for dependency injection in Java applications
  • Usage of Scala's concurrent ExecutionContext and Future for asynchronous programming

Description

Non compliant code

        import play.api.db.slick.DatabaseConfigProvider
import slick.jdbc.JdbcProfile

import javax.inject.Inject
import scala.concurrent.{ExecutionContext, Future}

class UserRepository @Inject()(dbConfigProvider: DatabaseConfigProvider)(implicit ec: ExecutionContext) {
  private val dbConfig = dbConfigProvider.get[JdbcProfile]

  import dbConfig._
  import profile.api._

  private class UserTable(tag: Tag) extends Table[User](tag, "user") {
    def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
    def name = column[String]("name")
    def email = column[String]("email")
    def * = (id, name, email) <> ((User.apply _).tupled, User.unapply)
  }

  private val users = TableQuery[UserTable]

  def list(): Future[Seq[User]] = db.run(users.result)

  def findByName(name: String): Future[Seq[User]] = {
    db.run(sql"SELECT * FROM user WHERE name = $name".as[User])
  }
}
        
        

The above code shows a basic user repository in a Scala backend application using the Play framework and Slick for database interaction. This repository has a findByName function that is vulnerable to Time-based SQL Injection attacks.

The findByName function constructs a SQL query by directly concatenating the user-provided name input into the query string. This is a dangerous practice as it opens up the possibility for an attacker to manipulate the SQL query.

For example, an attacker can provide a name such as "test' WAITFOR DELAY '00:00:10'--". This would result in the following SQL query:

                SELECT * FROM user WHERE name = 'test' WAITFOR DELAY '00:00:10'-
-


This query will cause the database to delay its response by 10 seconds, thus confirming to the attacker that the application is vulnerable to SQL Injection. The attacker can then proceed to exploit this vulnerability to extract sensitive information from the database or even modify its content.

The vulnerability arises from the lack of proper sanitization or parameterization of user-provided inputs before using them in SQL queries. It's a common and dangerous mistake that can lead to serious data breaches.

Steps

  • Use parameterized queries instead of concatenating user input directly into the SQL query.
  • Sanitize and validate user input before using it in the query.
  • Implement proper input validation to ensure that only expected values are accepted.
  • Use prepared statements or parameter binding to separate the SQL code from the user input.
  • Implement rate limiting or request throttling to prevent time-based attacks.
  • Regularly update and patch the database software to fix any known vulnerabilities.
  • Implement strong authentication and authorization mechanisms to prevent unauthorized access to the database.
  • Educate developers about secure coding practices and the risks associated with SQL injection vulnerabilities.

Compliant code

        import play.api.db.slick.DatabaseConfigProvider
import slick.jdbc.JdbcProfile

import javax.inject.Inject
import scala.concurrent.{ExecutionContext, Future}

class UserRepository @Inject()(dbConfigProvider: DatabaseConfigProvider)(implicit ec: ExecutionContext) {
  private val dbConfig = dbConfigProvider.get[JdbcProfile]

  import dbConfig._
  import profile.api._

  private class UserTable(tag: Tag) extends Table[User](tag, "user") {
    def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
    def name = column[String]("name")
    def email = column[String]("email")
    def * = (id, name, email) <> ((User.apply _).tupled, User.unapply)
  }

  private val users = TableQuery[UserTable]

  def list(): Future[Seq[User]] = db.run(users.result)

  def findByName(name: String): Future[Seq[User]] = {
    db.run(users.filter(_.name === name).result)
  }
}
        
        

The original code was vulnerable to Time-based SQL Injection attacks. This is because it was directly using user input in SQL queries without proper sanitization or parameterization. This could allow an attacker to inject malicious SQL code into the query, causing a delay in the execution of the queries or executing a heavy query that generates a time delay. By observing the time response, the attacker could deduce some information and determine if a vulnerability is present to exploit it.

The fixed code now uses parameterized queries to prevent SQL injection attacks. In the findByName method, instead of directly using the user input in the SQL query, it now uses the filter method provided by Slick, which automatically parameterizes the input. This separates the SQL code from the user input, preventing the possibility of SQL injection.

In addition to this, it's recommended to implement proper input validation to ensure that only expected values are accepted, use prepared statements or parameter binding to separate the SQL code from the user input, implement rate limiting or request throttling to prevent time-based attacks, regularly update and patch the database software to fix any known vulnerabilities, implement strong authentication and authorization mechanisms to prevent unauthorized access to the database, and educate developers about secure coding practices and the risks associated with SQL injection vulnerabilities.

References