Clear text storage of sensitive information

Summary

Sensitive information that is stored unencrypted is accessible to an attacker who gains access to the storage.

Description

Sensitive information that is stored unencrypted is accessible to an attacker who gains access to the storage. This can include sensitive information such as passwords, credit card numbers, and other personal information. If an attacker gains access to this information, they can use it for malicious purposes, such as identity theft or fraud.

In addition, sensitive information that is stored unencrypted can also be accessed by unauthorized users, such as employees or contractors who do not have a legitimate need to access the information. This can lead to data breaches and other security incidents.

To prevent this type of vulnerability, sensitive information should be stored in an encrypted format. This can include using encryption algorithms to encrypt the data before it is stored, as well as using secure storage mechanisms to protect the data from unauthorized access. Additionally, access to sensitive information should be restricted to only those users who have a legitimate need to access the information. This can include using access controls, such as role-based access control, to limit access to sensitive information to only those users who need it for their job functions.

By implementing these measures, organizations can help to protect sensitive information from unauthorized access and reduce the risk of data breaches and other security incidents.

Risk

How Can It Happen? Storing password as cleartext in a database or file is not a good idea. If an attacker gains access to the database or file, they can easily read the password and use it to gain access to the system.

Example

In 2021, a web site operated by PeopleGIS stored data of US municipalities in Amazon Web Service (AWS) Simple Storage Service (S3) buckets. A security researcher found 86 S3 buckets that could be accessed without authentication and stored data unencrypted. These buckets exposed over 1000 GB of data and 1.6 million files including physical addresses, phone numbers, tax documents, pictures of driver’s license IDs, etc. For more, See here and here.

Example Code (Bad Code)

The following example shows an incorrect way of storing user credentials in a cookie. In this case, the credentials are simply stored in cleartext.

...
using System.Text;
using System.Web;
using System.Web.Security;

public class CleartextStorageHandler : IHttpHandler
{

    public void ProcessRequest(HttpContext ctx)
    {
        string accountName = ctx.Request.QueryString["AccountName"];
        string password = ctx.Request.QueryString["Password"];
        // BAD: Setting a cookie value with cleartext sensitive data.
        ctx.Response.Cookies["AccountName"].Value = accountName;
        ctx.Response.Cookies["Password"].Value = password;
    }
}
...

Addressing Clear Text Storage of Sensitive Information

Depending on how the information is to be used in the application, generally there are two ways to store sensitive information securely.

  • Store the information in an encrypted format.
  • Store the information in a hashed format.
  • Store the information in a secure location, such as a secure database or secure storage service.

The last method has strong caveats, and will be not discussed in this exercise.

If the original information is to be used in the application, then it should be stored in an encrypted format. Consider the following sample code:

using System.Text;
using System.Web;
using System.Web.Security;

public class CleartextStorageHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext ctx)
    {
        string accountName = ctx.Request.QueryString["AccountName"];
        // GOOD: Encoding the value before setting it.
        ctx.Response.Cookies["AccountName"].Value = Protect(accountName, "Account name");
    }

    /// <summary>
    /// Protect the cleartext value, using the given type.
    /// </summary>
    /// <value>
    /// The protected value, which is no longer cleartext.
    /// </value>
    public string Protect(string value, string type)
    {
        return Encoding.UTF8.GetString(MachineKey.Protect(Encoding.UTF8.GetBytes(value), type));
    }
}

where we store the accountName in an encrypted format. The MachineKey.Protect method is used to encrypt the value before it is stored in the cookie.

If by design the original information is not to be used in the application, then it should be stored in a hashed format. A typical use case is to store passwords in a hashed format as to determine a password given is correct, we only need to compare the hash of the given password with the hash of the stored password. Consider the following sample code:

import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Base64;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

public class PasswordHashingExample {
    // Parameters for PBKDF2 hashing
    private static final int ITERATIONS = 65536;
    private static final int KEY_LENGTH = 256; // bits
    private static final int SALT_LENGTH = 16; // bytes
    private static final String ALGORITHM = "PBKDF2WithHmacSHA256";

    public static String hashPassword(String password) throws Exception {
        // Generate a random salt
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[SALT_LENGTH];
        random.nextBytes(salt);

        // Create the PBEKeySpec
        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
        
        // Get the SecretKeyFactory instance
        SecretKeyFactory factory = SecretKeyFactory.getInstance(ALGORITHM);
        
        // Generate the hash
        byte[] hash = factory.generateSecret(spec).getEncoded();
        
        // Combine salt and hash with separator for storage
        return Base64.getEncoder().encodeToString(salt) + ":" + 
              Base64.getEncoder().encodeToString(hash);
    }
}

Acknowledgement

This page is derived from the CodeQL project.