FlawOpen

flawopen.com/참고/Next.js Server Actions CSRF

Next.js Server Actions CSRF: Origin 검사와 안전한 설정

CWE-352: Cross-Site Request Forgery (CSRF)프레임워크 보안 가이드
요약

Next.js Server Actions는 POST를 사용하고 Origin의 호스트를 x-forwarded-host 또는 host와 비교합니다. 방문자 초대장에 적힌 장소가 서버가 예상한 장소와 같은지 확인하는 것과 같습니다. 이 검사는 교차 사이트 제출을 막는 데 도움이 되지만 권한을 부여하지 않습니다. 각 액션은 사용자를 인증하고 입력과 권한을 확인해야 합니다.

중요한 한계가 있습니다. 현재 Next.js 문서는 Origin이 없는 요청은 경고와 함께 통과시킨다고 설명합니다. 역방향 프록시도 비교 대상 호스트에 영향을 줍니다. 이 검사를 여러 방어 계층 중 하나로 보고, 프록시가 신뢰할 호스트 값을 설정하게 하세요.

Origin
브라우저가 전달하는 시작 페이지의 출처이며, Next.js는 그 호스트를 앱 호스트와 비교합니다.
Host / X-Forwarded-Host
비교에 사용하는 호스트 값입니다. 프록시가 전달 정보를 설정하거나 검증해야 합니다.
allowedOrigins
Server Actions가 허용하는 추가 Origin 호스트 목록입니다. 정확한 호스트와 와일드카드 패턴을 지원합니다.
Authorization
인증된 사용자가 이 특정 객체에서 해당 작업을 수행할 수 있는지 액션별로 판단합니다.

Server Actions의 기본 CSRF 검사 방식

1

브라우저가 Server Action을 제출

Server Action은 HTTP POST로 호출됩니다. SameSite 쿠키 동작은 많은 교차 사이트 제출을 기본 차단하지만 POST만으로 완전한 CSRF 방어가 되지는 않습니다.

2

Next.js가 호스트를 비교

Origin이 있으면 Next.js는 해당 호스트를 x-forwarded-host 또는 host와 비교합니다. 다르고 serverActions.allowedOrigins에도 없으면 거부합니다.

3

액션은 자체 정책도 적용

요청 검사 후 액션은 사용자를 인증하고 각 인자를 검증하며 작업 권한을 확인해야 합니다. 액션 ID와 숨겨진 폼 필드는 접근 제어가 아닙니다.

중요한 한계: Origin 누락과 allowedOrigins

문서화된 기본 동작은 Origin 누락을 거부하지 않음

현재 문서는 Origin이 없는 요청을 경고와 함께 계속 처리한다고 설명합니다. 모든 요청이 Origin 비교를 통과했다고 가정하지 마세요. 누락 원인을 조사하고 액션 안에서 인증, 권한 확인, 입력 검증을 수행하세요.

와일드카드는 신뢰 Origin 범위를 넓힘

allowedOrigins는 정확한 호스트와 와일드카드를 허용합니다. *는 레이블 하나, **는 하나 이상과 일치합니다. 조직이 관리하고 필요한 도메인과 하위 도메인만 허용하세요. 사용자 제어 하위 도메인까지 포함하면 통제할 수 없는 출처를 신뢰할 수 있습니다.

역방향 프록시를 보안 경계로 설정

Next.js는 x-forwarded-host 또는 host를 비교에 사용합니다. 엣지 프록시는 클라이언트가 보낸 전달 헤더를 제거하고 검증된 공개 호스트 이름으로 값을 설정해야 합니다. 앱은 신뢰할 프록시를 통해서만 접근 가능해야 합니다. 프록시가 내부 호스트를 전달한다면 필요한 공개 출처만 allowedOrigins에 추가하세요. 임의의 호스트를 신뢰해 불일치를 해결하지 마세요.

너무 넓은 신뢰
// Every subdomain, including delegated or tenant-controlled names, is trusted.
module.exports = {
  experimental: {
    serverActions: {
      allowedOrigins: ['**.example.com'],
    },
  },
};
신뢰 범위 제한
// Add only the public host that this deployment requires.
module.exports = {
  experimental: {
    serverActions: {
      allowedOrigins: ['admin.example.com'],
    },
  },
};

// Configure the edge to overwrite X-Forwarded-Host
// with the validated public hostname.

Host 권고문: CSRF와 구분되는 관련 위험

Host 신뢰 문제는 Server Actions의 SSRF 취약점으로 이어진 사례가 있습니다. CVE-2024-34351은 Host를 변경할 수 있고 액션이 /로 시작하는 상대 리디렉션을 수행하는 조건에서 14.1.1 이전 자체 호스팅 버전에 영향을 줬습니다. 별도의 2026년 7월 Vercel 권고문은 사용자 지정 서버에서 클라이언트가 호스트 관련 헤더를 제어할 때 SSRF와 내부 값 노출 가능성을 설명하며 수정 버전은 15.5.21과 16.2.11입니다. 이는 호스트 신뢰 취약점이지 Origin CSRF 검사 우회 증거가 아닙니다.

2026년 권고문 문제에는 수정 버전으로 업그레이드하세요. 그 전에는 엣지나 프록시에서 Host와 X-Forwarded-Host를 고정하거나 검증하세요. 권고문은 14.2.0 이상에서 추가 대안으로 __NEXT_PRIVATE_ORIGIN도 제시합니다. 적용 전 권고문에서 영향 및 지원 버전을 확인하세요.

모든 Server Action에서 권한 확인과 검증

Next.js 문서는 Server Action 인자를 신뢰할 수 없는 값으로 취급하고 현재 사용자가 실행 권한이 있는지 확인하라고 안내합니다. 취약한 예는 호출자가 전달한 객체 ID를 신뢰합니다. 수정 패턴은 인증과 ID 검증을 수행하고 데이터베이스 변경 자체에 소유자 조건을 포함합니다.

객체 권한 확인 없음
"use server";

export async function deleteInvoice(invoiceId: string) {
  await db.invoice.delete({ where: { id: invoiceId } });
}
인증, 검증, 권한 확인
"use server";

import { z } from "zod";
import { requireUser } from "@/auth";

const invoiceIdSchema = z.string().uuid();

export async function deleteInvoice(rawId: unknown) {
  const user = await requireUser();
  const invoiceId = invoiceIdSchema.parse(rawId);
  const result = await db.invoice.deleteMany({
    where: { id: invoiceId, ownerId: user.id },
  });
  if (result.count !== 1) throw new Error("Forbidden");
}

Server Actions CSRF 및 권한 확인 체크리스트

참고자료

함께 보기 사이트 간 요청 위조(CSRF, CWE-352): 동작 원리와 방어 방법 — flawopen.com SameSite=Strict가 CSRF 토큰을 대체하나요? GET 요청에 CSRF 토큰이 필요한가요? localStorage의 JWT와 HttpOnly 쿠키: 무엇이 더 안전한가 — flawopen.com