← 中文资源

简体中文版本

Cookie 前缀

Cookie 前缀 __Host- 与 __Secure- 让浏览器强制校验属性集:前者要求 Secure、无 Domain、Path 为斜杠,后者只要求 Secure,共同防住子域注入。

阅读英文原文

概念定义

Cookie 以名称、主机、路径为键,最具体匹配优先。攻击者控制任一子域就能设置与你会话同名、作用域为父域的 Cookie,把会话影子化。前缀把名称武器化:不满足严格约束,浏览器直接拒存。

工作机制

名为 __Host-session 的 Set-Cookie 只有在 Secure 已设、Domain 缺席(钉死精确主机)且 Path 为斜杠时才被存储,兄弟子域无法再影子化这个名字。__Secure- 只要求 Secure,适合必须带 Domain 作用域的 Cookie。

使用场景

可登录应用的会话、CSRF、偏好 Cookie,尤其是子域承载用户内容的平台。迁移期可改名并双读过渡。

常见误区

前缀是命名契约不是加密。声明错误的带前缀 Cookie 会被静默丢弃,集成测试必须断言 Set-Cookie 真的被存储。中间体与非浏览器客户端未必校验;旧浏览器会忽略前缀并以更弱属性接受。前缀与 HttpOnly、SameSite 是互补而非替代。

代码示例

export function createFlagSet(names) {
  if (!Array.isArray(names) || names.length === 0) {
    throw new TypeError("names must be a non-empty array");
  }
  if (names.length > 30) throw new RangeError("at most 30 flags are supported");
  const bits = new Map();
  const all = [];
  for (const name of names) {
    if (typeof name !== "string" || name.trim() === "") {
      throw new TypeError("flag names must be non-empty strings");
    }
    const normalized = name.trim();
    if (bits.has(normalized)) throw new Error(`duplicate flag "${normalized}"`);
    bits.set(normalized, 1 << bits.size);
    all.push(normalized);
  }
  const resolve = (name) => {
    const bit = bits.get(name);
    if (bit === undefined) throw new Error(`unknown flag "${name}"`);
    return bit;
  };
  return {
    names: () => [...all],
    mask(...selected) {
      return selected.reduce((mask, name) => mask | resolve(name), 0);
    },
    has(mask, name) {
      return (mask & resolve(name)) !== 0;
    },
    add(mask, name) {
      return mask | resolve(name);
    },
    remove(mask, name) {
      return mask & ~resolve(name);
    },
    toggle(mask, name) {
      return mask ^ resolve(name);
    },
    toNames(mask) {
      return all.filter((name) => (mask & resolve(name)) !== 0);
    },
  };
}

const permissions = createFlagSet(["read", "write", "admin"]);
let mask = permissions.mask("read", "write");
console.log(mask, permissions.toNames(mask));
mask = permissions.remove(mask, "write");
mask = permissions.toggle(mask, "admin");
console.log(permissions.has(mask, "write"), permissions.toNames(mask));
import { DatabaseSync } from 'node:sqlite';

const db = new DatabaseSync(':memory:');
db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT)');
db.prepare('INSERT INTO users (email) VALUES (?)').run('[email protected]');

function findEmails(ids) {
  if (ids.length === 0) return [];
  if (ids.some((id) => !Number.isInteger(id))) {
    throw new TypeError('ids must be integers');
  }

  const placeholders = ids.map(() => '?').join(', ');
  const stmt = db.prepare(
    'SELECT id, email FROM users WHERE id IN (' + placeholders + ')',
  );
  return stmt.all(...ids);
}

try {
  // 恶意输入只是参数值,永远不会被解析成 SQL 语法
  db.prepare('SELECT * FROM users WHERE email = ?').all("x' OR '1'='1");
} catch (err) {
  console.log('injection attempt contained:', err instanceof Error);
}

const rows = findEmails([1, 2, 3]);
console.log('matched rows:', rows.length);
db.close();