CWE-539: Use of Persistent Cookies Containing Sensitive Information - Remediation
In short CWE-539 is sensitive data, such as PII, stored in persistent cookies, which are saved across sessions. Prevent it by omitting sensitive data from cookies, masking it, and encrypting it before storage.
Introduction
Section titled “Introduction”CWE-539: Use of Persistent Cookies Containing Sensitive Information is a security vulnerability that involves the storage of sensitive data, such as Personally Identifiable Information (PII), in persistent cookies. These cookies, which are saved across sessions, can pose significant security risks if not handled properly. This article details the risks associated with PII exposure through CWE-539 and outlines remediation strategies, supported by coding examples in Java, JavaScript, and Python.
Understanding the Risks
Section titled “Understanding the Risks”Direct Risks
Section titled “Direct Risks”-
Unauthorized Access: Persistent cookies can be intercepted or accessed by unauthorized parties, especially if stored in plaintext.
-
Data Theft: If cookies containing PII are compromised, it can lead to identity theft and fraud.
-
Session Hijacking: Persistent cookies often store session information, which can be used for session hijacking if compromised.
Indirect Risks
Section titled “Indirect Risks”-
Reputational Damage: Breaches involving PII can damage an organization’s reputation, leading to a loss of customer trust and potential customer attrition.
-
Legal and Compliance Issues: Non-compliance with data protection laws (like GDPR and CCPA) due to improper handling of cookies can result in hefty fines and legal actions.
-
Operational Disruptions: Responding to and recovering from data breaches can consume significant resources and time.
Remediation Techniques
Section titled “Remediation Techniques”To mitigate the risks associated with storing sensitive information in persistent cookies, organizations should implement strategies like omitting sensitive data, masking data, and encrypting data before storage.
1. Omitting Data
Section titled “1. Omitting Data”Avoid storing sensitive or personally identifiable information in cookies altogether.
import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletResponse;
public class CookieManager { public void createSessionCookie(HttpServletResponse response, String sessionId) { Cookie sessionCookie = new Cookie("sessionID", sessionId); sessionCookie.setMaxAge(24 * 60 * 60); // Set for 24 hours response.addCookie(sessionCookie); // Ensure that no sensitive PII is stored in cookies }}// Express: HttpOnly cookies can only be set server-side, never via document.cookiefunction createSessionCookie(res, sessionId) { res.cookie('sessionID', sessionId, { httpOnly: true, secure: true, sameSite: 'strict', path: '/', maxAge: 86400000 }); // Do not store sensitive PII in cookies}from http.cookies import SimpleCookie
def create_session_cookie(session_id): cookie = SimpleCookie() cookie["sessionID"] = session_id cookie["sessionID"]["max-age"] = 86400 # Set for 24 hours cookie["sessionID"]["httponly"] = True print(cookie.output()) # Sensitive PII is not stored in the cookie2. Masking Data
Section titled “2. Masking Data”If any data must be stored in cookies and has potential privacy implications, it should be masked or anonymized.
import org.apache.commons.codec.digest.DigestUtils;
public class DataMasker { // PEPPER is loaded from your secret store, never hardcoded private static final String PEPPER = System.getenv("USER_ID_PEPPER");
public String maskUserId(String userId) { return "user-" + DigestUtils.sha256Hex(PEPPER + userId).substring(0, 12); }}const crypto = require('crypto');
// PEPPER is loaded from your secret store, never hardcodedconst PEPPER = process.env.USER_ID_PEPPER;
function maskUserId(userId) { return `user-${crypto.createHash('sha256').update(PEPPER + userId).digest('hex').slice(0, 12)}`;}import hashlibimport os
# PEPPER is loaded from your secret store, never hardcodedPEPPER = os.environ['USER_ID_PEPPER']
def mask_user_id(user_id): return 'user-' + hashlib.sha256((PEPPER + user_id).encode()).hexdigest()[:12]3. Encrypting Data
Section titled “3. Encrypting Data”Encrypt any sensitive information before storing it in a cookie to protect it from unauthorized access.
import javax.crypto.Cipher;import javax.crypto.SecretKey;import javax.crypto.spec.GCMParameterSpec;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletResponse;import java.nio.ByteBuffer;import java.nio.charset.StandardCharsets;import java.security.SecureRandom;
public class EncryptionUtility { // key is supplied by the caller, sourced from your KMS or secret store public static void encryptAndAddCookie(HttpServletResponse response, String data, SecretKey key) throws Exception { byte[] iv = new byte[12]; new SecureRandom().nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, iv)); byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
byte[] payload = ByteBuffer.allocate(iv.length + encryptedData.length).put(iv).put(encryptedData).array(); String encryptedString = java.util.Base64.getEncoder().encodeToString(payload);
Cookie secureCookie = new Cookie("secureData", encryptedString); secureCookie.setHttpOnly(true); secureCookie.setMaxAge(24 * 60 * 60); response.addCookie(secureCookie); }}const crypto = require('crypto');
// Express: the cookie is set server-side so HttpOnly is actually honored// key: 32-byte Buffer loaded from your KMS or secret storefunction encryptAndAddCookie(res, data, key) { const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); const encrypted = Buffer.concat([cipher.update(data, 'utf8'), cipher.final()]); const value = iv.toString('hex') + ':' + encrypted.toString('hex'); res.cookie('secureData', value, { httpOnly: true, secure: true, sameSite: 'strict', path: '/', maxAge: 86400000 });}from cryptography.fernet import Fernetfrom http.cookies import SimpleCookie
# key: Fernet key loaded from your secret store, never generated per calldef encrypt_and_add_cookie(data, key): cipher_suite = Fernet(key) encrypted_data = cipher_suite.encrypt(data.encode())
cookie = SimpleCookie() cookie["secureData"] = encrypted_data.decode() cookie["secureData"]["max-age"] = 86400 cookie["secureData"]["httponly"] = True print(cookie.output())Detecting this with HoundDog.ai
Section titled “Detecting this with HoundDog.ai”HoundDog.ai’s Privacy Code Scanner (hounddog scan) reports CWE-539, together with CWE-315, when unsanitized sensitive
data reaches the Cookies sink. It identifies sensitive data elements in source code and traces them through assignments,
transformations, string interpolations, and function calls to the sinks they reach.
-
Sensitive data types. The scanner ships with over 100 data element definitions covering common PII, PHI, CHD, PIFI, and secret data (tags
PII,PHI,CHD,PIFI,SECRET); the full list is in the scanner’s data-elements.md. A finding names the data element involved, such as Email or Social Security Number. -
Sinks. CWE-315 and CWE-539 are both reported on the Cookies data sink, which is risky by default. By default the scanner allowlists
auth-token,email,username, andpasswordfor this sink, so those data elements in cookies are not flagged; remove entries from the allowlist under Scanner Rules if your policy is stricter. -
Languages. Dataflows are reported for Python, JavaScript, TypeScript, Java, C#/.NET, and Go. Other supported languages are scanned for data elements only and produce no dataflows. See Supported Languages.
-
Sanitizers. If the data passes through a recognized sanitizer (masking, hashing, encryption, redaction) before it reaches the sink, the dataflow is treated as safe and is not flagged. Organizations can add their own patterns under Sanitizers.
For example, scanning a TypeScript file that writes a Social Security Number to a cookie with the js-cookie library
produces a finding like this (console output, excerpt; the file path is shortened):
Cookies.set("profile", user.ssn);🟥 CRITICAL: Social Security Number sent to Cookies in /path/to/project/app.ts:4:3
4 │ Cookies.set("profile", user.ssn)
Associated Compliance Frameworks────────────────────────────────CWE: CWE-315, CWE-539OWASP: A04:2021Privacy: CCPA, GDPR-A5-32, NIST-800-53To run a scan, see the quickstart. For how data elements, data sinks, and sanitizers are defined and customized, see the scanner rules reference.
Conclusion
Section titled “Conclusion”Mitigating the risks associated with CWE-539 is essential for protecting PII and maintaining the security of web applications. By implementing effective cookie management strategies such as omitting sensitive data, masking, and encrypting information, organizations can enhance their compliance with privacy regulations and secure the data of their users.
