기술 심층 분석: React Server Components가 어떻게 작동하고 취약점이 어디에 나타나는지

발행: (2025년 12월 16일 오전 05:42 GMT+9)
8 min read
원문: Dev.to

Source: Dev.to

간단하지만 정확한 RSC 요청 라이프사이클

  1. 들어오는 요청이 RSC 엔드포인트에 도달

    • Next.js에서는 보통 /_rsc와 같은 내부 라우트에 매핑됩니다.
    • 요청은 인증 및 애플리케이션 미들웨어가 실행되기 전에 처리됩니다.
  2. 요청 본문 파싱 및 프로토콜 디코딩

    • 페이로드는 React Flight 프로토콜에 따라 파싱됩니다.
    • 여기에는 직렬화된 컴포넌트 레퍼런스, props, 실행 힌트의 디코딩이 포함됩니다.
    • 이 단계에서 React는 페이로드 형태가 충분히 유효하다고 가정합니다.
  3. 컴포넌트 그래프 재구성

    • React는 직렬화된 입력으로부터 서버 컴포넌트 트리를 재구성합니다.
    • 모듈 레퍼런스를 해결하고 로드합니다.
    • 비동기 경계와 Suspense 세그먼트를 등록합니다.
  4. 서버 렌더링 및 스트리밍

    • 컴포넌트가 서버에서 실행됩니다.
    • 출력이 다시 직렬화되어 청크 단위로 클라이언트에 스트리밍됩니다.
    • 오류 경계와 부분 결과가 점진적으로 플러시됩니다.
  5. 오류 및 복구

    • 스트림 중에 예외가 발생하면 React는 우아하게 복구하려 시도합니다.
    • 메타데이터, 스택 트레이스, 모듈 식별자는 여전히 내부적으로 사용 가능합니다.
    • 각 단계는 잘못된 형식이거나 악의적인 입력에 민감합니다.

서비스 거부(DoS) 공격이 발생하는 방식

DoS 문제는 주로 2단계와 3단계에서 발생하며, 애플리케이션 로직이 관여하기 전입니다.

악의적인 요청은 다음을 할 수 있습니다:

  • 직렬화된 페이로드 크기를 부풀리기
  • 깊게 중첩되거나 순환하는 레퍼런스 삽입
  • 과도한 비동기 경계 해석 강제

개념적 예시 (JSON 페이로드):

POST /_rsc
Content-Type: application/json

{
  "type": "ServerComponent",
  "props": {
    "children": {
      "children": {
        "children": {
          "...": "..."
        }
      }
    }
  }
}

페이로드가 구문적으로 유효하더라도 React는:

  • 이를 역직렬화하려 시도하고
  • 메모리 내 컴포넌트 그래프를 구축하며
  • 실행 및 스트리밍 작업을 스케줄링합니다.

그 결과:

  • 디코딩 중 CPU 사용량 급증
  • 그래프 재구성 시 메모리 압박
  • 워커 또는 이벤트 루프 포화

핵심은 사용자 정의 코드에 도달하거나 인증을 거치지 않고도 발생한다는 점이며, 전통적인 애플리케이션 레이어 방어 가정이 크게 무효화됩니다.

소스 코드 노출이 발생할 수 있는 이유

소스 코드 노출은 4단계와 5단계에서 React가 오류를 처리하는 방식의 부수 효과입니다.

렌더링 또는 직렬화 오류가 스트림 중에 발생하면:

  • React는 내부 모듈 경로에 대한 레퍼런스를 여전히 보유하고
  • 스택 트레이스에 해결된 파일 위치가 포함되며
  • 컴포넌트 메타데이터가 부분적으로 직렬화됩니다.

특정 엣지 케이스에서는 이 정보가:

  • 스트리밍 응답에 누출되거나
  • 오류 페이로드에 포함되거나
  • 스트림이 정상적으로 중단되기 전에 플러시될 수 있습니다.

누출된 정보 예시 (JavaScript 오류):

Error: Failed to resolve Server Component
  at /app/src/components/admin/UserList.server.tsx
  at react-server-dom-webpack/server

이로 인해 다음이 드러납니다:

  • 프로젝트 디렉터리 구조
  • 서버 전용 컴포넌트 경계
  • 내부 모듈 레이아웃 및 명명 규칙

단독으로는 익스플로잇이 아니지만, 타깃 공격을 위한 사전 작업 비용을 크게 낮춥니다.

서버 액션이 필요하지 않은 이유

많은 사람들이 이 위험이 서버 액션을 사용할 때만 발생한다고 오해합니다. 실제로는:

  • 서버 액션은 API 표면일 뿐이며
  • RSC는 런타임 및 프로토콜입니다.

다음과 같은 간단한 페이지라도:

export default async function Page() {
  return ;
}

다음 과정을 거칩니다:

  • 요청 역직렬화
  • 컴포넌트 그래프 재구성
  • 서버 실행 및 스트리밍

취약 로직은 서버 액션이 정의되어 있든 없든 실행됩니다.

주요 요점

  • React Server Components는 요청 역직렬화, 컴포넌트 그래프 재구성, 스트리밍 기반 렌더링을 하나의 프레임워크 관리 파이프라인으로 결합하는 근본적으로 새로운 서버‑사이드 실행 모델을 도입합니다.
  • 실제 공격 표면은 인증 이전, 애플리케이션 미들웨어 이전, 사용자 정의 비즈니스 로직 실행 이전에 존재합니다.
  • 형식이 잘못되었거나 악의적인 RSC 요청은 다음을 초래할 수 있습니다:
    • 서비스 거부 – 역직렬화 및 컴포넌트 그래프 재구성 중 과도한 CPU 사용, 메모리 압박, 워커·이벤트 루프 포화
    • 서버‑사이드 구현 세부 정보의 우발적 노출 – 스트리밍 오류 처리 과정에서 내부 파일 경로, 컴포넌트 경계, 모듈 레이아웃 등이 노출
  • 이러한 문제는 잘못 구성된 애플리케이션이나 안전하지 않은 사용자 코드 때문이 아니라 React Server Components 런타임 자체의 프레임워크 수준 취약점이며, 애플리케이션 레이어 방어만으로는 신뢰성 있게 완화할 수 없습니다.

공식 권고 및 패치

Back to Blog

관련 글

더 보기 »