Name: Cleartext storage of sensitive information in an SQLite database

Description:

Storing sensitive information in a non-encrypted database can expose it to an attacker.

ID: cpp/cleartext-storage-database

Kind: problem

Severity: warning

Precision: medium

/**
 * @name Cleartext storage of sensitive information in an SQLite database
 * @description Storing sensitive information in a non-encrypted
 *              database can expose it to an attacker.
 * @kind problem
 * @problem.severity warning
 * @precision medium
 * @id cpp/cleartext-storage-database
 * @tags security
 *       external/cwe/cwe-313
 */

import cpp
import semmle.code.cpp.security.SensitiveExprs
import semmle.code.cpp.security.TaintTracking

class UserInputIsSensitiveExpr extends SecurityOptions {
  override predicate isUserInput(Expr expr, string cause) {
    expr instanceof SensitiveExpr and cause = "sensitive information"
  }
}

class SqliteFunctionCall extends FunctionCall {
  SqliteFunctionCall() {
    this.getTarget().getName().matches("sqlite%")
  }

  Expr getASource() {
    result = this.getAnArgument()
  }
}

predicate sqlite_encryption_used() {
  any(StringLiteral l).getValue().toLowerCase().regexpMatch("pragma key.*") or
  any(StringLiteral l).getValue().toLowerCase().matches("%attach%database%key%") or
  any(FunctionCall fc).getTarget().getName().matches("sqlite%\\_key\\_%")
}

from SensitiveExpr taintSource, Expr taintedArg, SqliteFunctionCall sqliteCall
where tainted(taintSource, taintedArg)
  and taintedArg = sqliteCall.getASource()
  and not sqlite_encryption_used()
select sqliteCall, "This SQLite call may store $@ in a non-encrypted SQLite database",
       taintSource, "sensitive information"

Sensitive information that is stored in an unencrypted SQLite database is accessible to an attacker who gains access to the database.

Recommendation

Ensure that if sensitive information is stored in a database then the database is always encrypted.

Example

The following example shows two ways of storing information in an SQLite database. In the 'BAD' case, the credentials are simply stored in cleartext. In the 'GOOD' case, the database (and thus the credentials) are encrypted.

     1void bad(void) {
     2  char *password = "cleartext password";
     3  sqlite3 *credentialsDB;
     4  sqlite3_stmt *stmt;
     5
     6  if (sqlite3_open("credentials.db", &credentialsDB) == SQLITE_OK) {
     7    // BAD: database opened without encryption being enabled
     8    sqlite3_exec(credentialsDB, "CREATE TABLE IF NOT EXISTS creds (password TEXT);", NULL, NULL, NULL);
     9    if (sqlite3_prepare_v2(credentialsDB, "INSERT INTO creds(password) VALUES(?)", -1, &stmt, NULL) == SQLITE_OK) {
    10      sqlite3_bind_text(stmt, 1, password, -1, SQLITE_TRANSIENT);
    11      sqlite3_step(stmt);
    12      sqlite3_finalize(stmt);
    13      sqlite3_close(credentialsDB);
    14    }
    15  }
    16}
    17
    18void good(void) {
    19  char *password = "cleartext password";
    20  sqlite3 *credentialsDB;
    21  sqlite3_stmt *stmt;
    22
    23  if (sqlite3_open("credentials.db", &credentialsDB) == SQLITE_OK) {
    24    // GOOD: database encryption enabled:
    25    sqlite3_exec(credentialsDB, "PRAGMA key = 'secretKey!'", NULL, NULL, NULL);
    26    sqlite3_exec(credentialsDB, "CREATE TABLE IF NOT EXISTS creds (password TEXT);", NULL, NULL, NULL);
    27    if (sqlite3_prepare_v2(credentialsDB, "INSERT INTO creds(password) VALUES(?)", -1, &stmt, NULL) == SQLITE_OK) {
    28      sqlite3_bind_text(stmt, 1, password, -1, SQLITE_TRANSIENT);
    29      sqlite3_step(stmt);
    30      sqlite3_finalize(stmt);
    31      sqlite3_close(credentialsDB);
    32    }
    33  }
    34}
void bad(void) {
  char *password = "cleartext password";
  sqlite3 *credentialsDB;
  sqlite3_stmt *stmt;

  if (sqlite3_open("credentials.db", &credentialsDB) == SQLITE_OK) {
    // BAD: database opened without encryption being enabled
    sqlite3_exec(credentialsDB, "CREATE TABLE IF NOT EXISTS creds (password TEXT);", NULL, NULL, NULL);
    if (sqlite3_prepare_v2(credentialsDB, "INSERT INTO creds(password) VALUES(?)", -1, &stmt, NULL) == SQLITE_OK) {
      sqlite3_bind_text(stmt, 1, password, -1, SQLITE_TRANSIENT);
      sqlite3_step(stmt);
      sqlite3_finalize(stmt);
      sqlite3_close(credentialsDB);
    }
  }
}

void good(void) {
  char *password = "cleartext password";
  sqlite3 *credentialsDB;
  sqlite3_stmt *stmt;

  if (sqlite3_open("credentials.db", &credentialsDB) == SQLITE_OK) {
    // GOOD: database encryption enabled:
    sqlite3_exec(credentialsDB, "PRAGMA key = 'secretKey!'", NULL, NULL, NULL);
    sqlite3_exec(credentialsDB, "CREATE TABLE IF NOT EXISTS creds (password TEXT);", NULL, NULL, NULL);
    if (sqlite3_prepare_v2(credentialsDB, "INSERT INTO creds(password) VALUES(?)", -1, &stmt, NULL) == SQLITE_OK) {
      sqlite3_bind_text(stmt, 1, password, -1, SQLITE_TRANSIENT);
      sqlite3_step(stmt);
      sqlite3_finalize(stmt);
      sqlite3_close(credentialsDB);
    }
  }
}

References