FlawOpen
● Изоляция serverless-запросов

Утечка serverless-состояния: храните данные в рамках запроса

Как повторное использование serverless-среды может открыть старые данные и как изолировать идентичность и состояние каждого запроса.

💡 Простое объяснение (ELI5)

Представьте аутентификацию, идентификатор tenant и данные запроса как пропуск посетителя: он относится к одному визиту и не должен оставаться на общем рабочем столе. Некоторые serverless-среды повторно используют среду выполнения или isolate для следующих запросов. Если код сохраняет пользовательский контекст в изменяемой переменной уровня модуля, а другой запрос читает её, приложение может нарушить собственную границу между пользователями или tenant.

Повторное использование — особенность реализации, а не гарантия, что следующий пользователь попадёт в ту же среду или что она останется активной фиксированное время. Для утечки нужны общий контекст выполнения и путь в коде, который сохраняет данные запроса или читает устаревшее состояние. Храните идентичность в пределах запроса и явно передавайте её нужным операциям.

Основные понятия и термины

Execution environment reuse
Повторное использование среды: платформа может сохранить runtime или isolate для следующих вызовов, но может завершить его в любой момент.
Module-level state
Состояние уровня модуля: значение, инициализированное вне обработчика. Оно может сохраниться в этой среде между вызовами и не должно содержать пользовательские или tenant-данные.
AsyncLocalStorage
AsyncLocalStorage: API Node.js для распространения хранилища в асинхронном контексте. Он отслеживает контекст, но не аутентифицирует и не авторизует пользователя.
Cross-request data exposure
Межзапросное раскрытие данных: последующий запрос читает данные предыдущего в той же повторно используемой среде. Само повторное использование не доказывает утечку.

Пошаговая цепочка раскрытия данных

Step 1

Запрос создает собственные данные

Запрос A проходит аутентификацию и получает контекст пользователя или tenant. Ошибка возникает, если код записывает эту идентичность в изменяемую переменную уровня модуля, а не хранит в обработчике.

Step 2

Среда может быть использована повторно

После ответа платформа может сохранить среду активной. AWS описывает повторное использование; Cloudflare Workers может использовать isolate повторно, но маршрутизация и срок жизни не образуют фиксированную последовательность между пользователями.

Step 3

Следующий запрос попадает в ту же среду

Если запрос B обслуживается тем же процессом или isolate, значение уровня модуля от A остается доступным до перезаписи или очистки. Ранний выход или пропущенная аутентификация могут открыть доступ к старому состоянию.

Step 4

Приложение возвращает данные с чужой идентичностью

Если обработчик использует устаревшее значение в запросе к БД или ответе, B может получить данные A. Это условная ошибка изоляции приложения, а не неизбежное свойство serverless-платформ.

Исходный код: опасный шаблон и безопасный вариант

НЕБЕЗОПАСНО: ОБЩЕЕ СОСТОЯНИЕ
// UNSAFE: request identity is mutable process-wide state.
import { NextResponse } from "next/server";

let cachedUserContext: { userId: string; role: string } | null = null;

export async function POST(req: Request) {
  const token = req.headers.get("authorization");

  if (token) {
    cachedUserContext = await decodeUser(token);
  }

  // If token is missing, stale state can still be used.
  return NextResponse.json({ actor: cachedUserContext?.userId });
}
БЕЗОПАСНЕЕ: ЛОКАЛЬНАЯ ИДЕНТИЧНОСТЬ
// SAFE PATTERN: keep identity local and pass it to the service.
import { NextResponse } from "next/server";

export async function POST(req: Request) {
  const user = await authenticate(req);
  if (!user) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const account = await loadAccountForUser(user.id);
  return NextResponse.json({ actor: user.id, account });
}

// The service enforces ownership in its query:
async function loadAccountForUser(userId: string) {
  return db.account.findFirst({ where: { ownerId: userId } });
}

Контрольный список усиления защиты

← Открыть полный каталог безопасности Справочные руководства →

Источники

См. также PHP == и сравнение magic hash Безопасен ли Rust с блоком unsafe? Безопаснее ли strncpy(), чем strcpy()? CWE и CVE: в чём разница? Mass assignment: риски и безопасные списки полей