Skip to main content

Business information leak - DB

Need

Protection of sensitive business information stored in the database

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.db for database operations in Play Framework
  • Usage of Anorm for type-safe database access in Scala

Description

Non compliant code

import play.api.mvc._
import play.api.db._
import anorm._

class Application @Inject()(db: Database, cc: ControllerComponents) extends AbstractController(cc) {

def getUserTransactions = Action { implicit request: Request[AnyContent] =>
db.withConnection { implicit connection =>
val sqlQuery = SQL("SELECT * FROM transactions WHERE user_id = {user_id}")
val transactions = sqlQuery.on('user_id -> request.session.get("user_id")).executeQuery().as(Macro.namedParser[Transaction].*)
Ok(Json.toJson(transactions))
}
}

def getWalletDetails = Action { implicit request: Request[AnyContent] =>
db.withConnection { implicit connection =>
val sqlQuery = SQL("SELECT * FROM wallets WHERE user_id = {user_id}")
val wallet = sqlQuery.on('user_id -> request.session.get("user_id")).executeQuery().as(Macro.namedParser[Wallet].*)
Ok(Json.toJson(wallet))
}
}

// Other endpoints...
}

The above code snippet is an example of a Scala backend application using the Play framework. This code is vulnerable to business information leakage.

The getUserTransactions and getWalletDetails endpoints are exposed and they directly use the user_id from the session to retrieve sensitive information from the database. This information includes transaction details and wallet details, which could include associated credit card information and available balance.

Moreover, the SQL queries used to retrieve this information are not parameterized, which could potentially lead to SQL injection attacks if an attacker is able to manipulate the session data.

The code does not implement any form of access control or data filtering, meaning that all transactions and wallet details for the specified user are returned in the response. This exposes a large amount of sensitive business information.

If an attacker is able to hijack a user's session, they would be able to retrieve all of this information. Additionally, if the session data is not properly secured, an attacker could potentially guess or brute-force user_id values and retrieve information for other users.

The code also does not implement any form of encryption or hashing for sensitive data. This means that all data is stored and transmitted in plaintext, which could be easily read if intercepted or accessed.

Steps

  • Avoid storing sensitive information in the source code.
  • Implement proper authentication and authorization mechanisms to control access to sensitive data.
  • Use parameterized queries or prepared statements to prevent SQL injection attacks.
  • Encrypt sensitive data before storing it in the database.
  • Implement access controls to restrict the visibility of sensitive data to authorized users only.
  • Regularly review and update access controls to ensure they are still effective.
  • Implement logging and monitoring mechanisms to detect and respond to unauthorized access attempts.
  • Regularly update and patch the application and its dependencies to address any security vulnerabilities.
  • Perform regular security assessments and penetration testing to identify and fix vulnerabilities.
  • Educate developers about secure coding practices and provide training on secure development techniques.

Compliant code

import play.api.mvc._
import play.api.db._
import anorm._
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
import java.util.Base64

class Application @Inject()(db: Database, cc: ControllerComponents) extends AbstractController(cc) {

private val secretKey = "my-secret-key" // This should be stored securely and not in the source code

def getUserTransactions = Action { implicit request: Request[AnyContent] =>
db.withConnection { implicit connection =>
val sqlQuery = SQL("SELECT * FROM transactions WHERE user_id = {user_id}")
val transactions = sqlQuery.on('user_id -> decrypt(request.session.get("user_id"))).executeQuery().as(Macro.namedParser[Transaction].*)
Ok(Json.toJson(transactions))
}
}

def getWalletDetails = Action { implicit request: Request[AnyContent] =>
db.withConnection { implicit connection =>
val sqlQuery = SQL("SELECT * FROM wallets WHERE user_id = {user_id}")
val wallet = sqlQuery.on('user_id -> decrypt(request.session.get("user_id"))).executeQuery().as(Macro.namedParser[Wallet].*)
Ok(Json.toJson(wallet))
}
}

// Other endpoints...

private def encrypt(value: String): String = {
val cipher = Cipher.getInstance("AES/ECB/PKCS5Padding")
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(secretKey.getBytes("UTF-8"), "AES"))
Base64.getEncoder.encodeToString(cipher.doFinal(value.getBytes("UTF-8")))
}

private def decrypt(value: String): String = {
val cipher = Cipher.getInstance("AES/ECB/PKCS5Padding")
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(secretKey.getBytes("UTF-8"), "AES"))
Base64.getDecoder.decode(value.getBytes("UTF-8")).toString
}
}

The updated code includes encryption and decryption methods to secure sensitive data. The encrypt method is used to encrypt sensitive data before storing it in the database, and the decrypt method is used to decrypt the data when it is retrieved from the database.

The secret key used for encryption and decryption is stored as a private variable within the class. However, it is recommended to store this key securely and not in the source code.

The getUserTransactions and getWalletDetails methods have been updated to use the decrypt method to decrypt the user_id from the session before using it in the SQL query. This prevents the raw user_id from being exposed in the source code.

Please note that this is a basic implementation of encryption and decryption. For a production-grade application, it is recommended to use a more secure method of encryption and key management.

Also, it is important to implement proper authentication and authorization mechanisms to control access to sensitive data. Regularly review and update access controls to ensure they are still effective. Implement logging and monitoring mechanisms to detect and respond to unauthorized access attempts. Regularly update and patch the application and its dependencies to address any security vulnerabilities. Perform regular security assessments and penetration testing to identify and fix vulnerabilities. Educate developers about secure coding practices and provide training on secure development techniques.

References