FlawOpen
● Serverless 요청 격리

Serverless 상태 누출: 사용자 데이터는 요청 범위에

재사용되는 serverless runtime이 오래된 사용자 데이터를 노출하는 조건과 요청별로 신원과 상태를 분리하는 방법을 설명합니다.

💡 쉬운 설명

인증 정보, tenant 신원, 요청별 데이터는 방문자 출입증처럼 한 번의 방문에 속하며 공유 작업대에 남겨 두면 안 됩니다. 일부 serverless runtime은 후속 요청에 실행 환경이나 isolate를 재사용합니다. 코드가 사용자 context를 모듈 수준의 변경 가능한 상태에 저장하고 다른 요청이 이를 읽으면 사용자 또는 tenant 경계를 넘을 수 있습니다.

재사용은 구현상 가능한 동작이지 다음 사용자가 같은 runtime에 도달한다거나 일정 시간 유지된다는 보장이 아닙니다. 누출에는 공유 실행 상태와 요청 데이터를 보존하거나 오래된 상태를 읽는 코드 경로가 모두 필요합니다. 신원은 요청 안에 두고 필요한 작업에 명시적으로 전달하세요.

핵심 개념과 용어

Execution environment reuse
실행 환경 재사용: 플랫폼은 이후 호출을 위해 runtime 또는 isolate를 유지할 수 있지만 언제든 폐기할 수 있습니다.
Module-level state
모듈 수준 상태: handler 밖에서 초기화한 값입니다. 동일 runtime에서 호출 간 남을 수 있으므로 사용자/tenant별 데이터를 담으면 안 됩니다.
AsyncLocalStorage
AsyncLocalStorage: 비동기 실행 context에 store를 전달하는 Node.js API입니다. context를 추적할 뿐 사용자를 인증하거나 권한을 부여하지 않습니다.
Cross-request data exposure
요청 간 데이터 노출: 나중 요청이 재사용된 context에서 이전 요청의 데이터를 읽는 실패입니다. 재사용만으로 누출이 입증되지는 않습니다.

데이터 노출 단계별 흐름

Step 1

요청 하나가 자체 데이터를 생성

요청 A는 인증을 거쳐 사용자 또는 tenant context를 확인합니다. 이를 handler 안에 두지 않고 모듈 수준 변경 변수에 쓰면 문제가 생깁니다.

Step 2

runtime이 재사용될 수 있음

응답 뒤 플랫폼이 환경을 따뜻하게 유지할 수 있습니다. AWS는 환경 재사용을 문서화합니다. Cloudflare Workers도 isolate를 재사용할 수 있지만 사용자 간 라우팅과 수명은 정해진 순서가 아닙니다.

Step 3

다음 요청이 같은 runtime에 도달

요청 B가 같은 프로세스나 isolate에서 처리되면 A가 남긴 모듈 값은 덮어쓰거나 지우기 전까지 남습니다. 조기 반환이나 인증 누락이 오래된 상태를 다시 사용하게 만들 수 있습니다.

Step 4

애플리케이션이 잘못된 신원으로 데이터를 반환

handler가 오래된 값으로 DB 조회나 응답을 만들면 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: 위험과 안전한 필드 허용 목록