React RCE 취약점(CVE-2025-55182) – 문제와 발생 원인

2025년 12월 초 공개된 React Server Components 취약점(CVE-2025-55182, 일명 React2Shell / React4Shell)은 CVSS 10.0이 매겨진, 사전 인증 없이 원격 코드 실행(RCE)이 가능한 매우 심각한 이슈다. 이미 공개 PoC와 실제 스캔·공격 시도가 관측되고 있다.

이 글은 “누가, 왜, 어디까지 위험한지”를 조금 더 체계적으로 정리한 버전이다.


한눈에 보는 요약



  • 문제의 핵심 React Server Components(RSC)가 사용하는 Flight 프로토콜 역직렬화 로직의 결함 때문에, 공격자가 조작한 요청을 보내면 서버에서 임의 JS 코드가 실행될 수 있다.

  • 영향 받는 주요 구성요소

  • react-server-dom-webpack / -parcel / -turbopack 19.0 / 19.1.0 / 19.1.1 / 19.2.0

  • 이들을 내장한 Next.js 15.x, 16.x, 14.3.0-canary.77 이후 버전(App Router 사용 시)

  • 브라우저에서만 도는 “전통적 React SPA” RSC/Server Actions/서버 렌더링을 전혀 사용하지 않는 순수 클라이언트 React 앱은 영향 없음.

  • ** 독자 중에 과거에 Next.js를 사용했다면 당장 해야 할 일**

  1. 프로젝트에서 react-server-dom-* / next 버전 확인
  2. React는 19.0.1 / 19.1.2 / 19.2.1 이상으로 업데이트
  3. Next.js는 공지된 패치 버전(15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7 등)으로 업데이트 또는 안정 버전으로 다운그레이드
  4. 이미 인터넷에 노출된 서버라면, 로그·WAF 룰·이상 징후를 함께 점검

image

이번에 발견된 취약점은 무엇인가

React 팀이 공개한 내용만 정리하면, 이번 취약점은 다음과 같이 요약된다.

  • 취약 대상: React Server Components(RSC) 구현체(react-server-dom-*)
  • 취약 구간: 클라이언트 → 서버로 RSC 데이터를 전송할 때 사용하는 Flight 프로토콜 디코딩(역직렬화) 로직
  • 공격 방법:

  • 공격자가 특수하게 조작된 Flight 페이로드를 RSC/Server Actions HTTP 엔드포인트로 전송

  • 서버가 이 데이터를 “신뢰”하고 역직렬화하는 과정에서, 내부 모듈 로딩/객체 생성 로직이 공격자 입력에 끌려다님
  • 그 결과, 서버 환경에서 임의 코드 실행(RCE) 가능

중요한 점은, 로그인/세션 없이도(Unauthenticated) 공격이 가능하다는 것이다. 외부에 노출된 Next.js RSC 서버라면 “인터넷에 열려 있는 순간부터” 스캔·공격 대상이 된다.


취약점이 발생하는 정확한 조건



1. React/Next.js 버전 조건

  • React RSC 관련 패키지

  • react-server-dom-webpack

  • react-server-dom-parcel
  • react-server-dom-turbopack

  • 취약 버전

  • 19.0, 19.1.0, 19.1.1, 19.2.0

  • 패치 버전

  • 19.0.1, 19.1.2, 19.2.1

  • Next.js (App Router 기반)

  • 취약:

    • 14.3.0-canary.77 이상
    • 15.x, 16.x (RSC를 포함하는 기본 App Router 구조)
    • 패치:

    • 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7 등

2. 아키텍처/런타임 조건

다음 조건이 모두 맞을 때 실제로 공격 가능성이 높다.

  1. RSC 사용 * Next.js App Router, 또는 다른 RSC 지원 프레임워크 사용

  2. 서버에서 React 코드 실행 * Node.js 등에서 React Server Components/Server Actions가 돌아가는 구조

  3. HTTP로 외부에 노출된 서버 * 인터넷 또는 내부망에서라도 공격자가 HTTP 요청을 보낼 수 있는 환경

반대로, 다음과 같은 환경은 이번 취약점과 거의 무관하다.

  • CRA/Vite 기반 순수 클라이언트 React SPA
  • RSC/Server Actions를 전혀 쓰지 않고, 단순 정적 번들만 S3, CDN 등에 올리는 방식
  • “React는 오직 브라우저에서만” 실행되고, 서버는 완전히 별개의 기술 스택인 구조(예: React + Django REST, React + FastAPI)

React-DRF, React-FastAPI 조합은 왜 상대적으로 안전했는가

React를 Django REST Framework 또는 FastAPI 같은 파이썬 백엔드와 조합해 사용하는 구조에서는 문제가 발생하지 않는다. 이유는 다음과 같다.

  1. React 코드가 서버에서 실행되지 않는다
  • React는 브라우저에서만 돌고, Django/FastAPI는 순수 HTTP API 서버 역할만 수행
  • 서버는 React의 Flight 프로토콜을 이해할 필요도, RSC를 해석할 필요도 없다.
  1. 통신 경계가 명확하다
  • React ↔ 서버 간 통신은 JSON/HTTP API로 명확히 구분되고,
  • 서버는 “React 내부 모듈”을 require()하거나, 클라이언트가 넘긴 metadata 값으로 모듈을 로딩하지 않는다.
  1. RSC·Server Actions·Flight 프로토콜을 사용하지 않는다
  • 이번 취약점의 공격 표면은 “RSC Flight 페이로드를 해석하는 서버 쪽 React 코드”에 있다.
  • Django/FastAPI는 해당 코드를 아예 포함하지 않는다.

결국, 이번 사고는 “React + 서버 통합 프레임워크(Next.js 등)에서만 발생할 수 있는 구조적 문제”였다. 역할이 분리된 React-SPA + REST API 조합은 애초에 문제가 되는 코드 경로가 존재하지 않았다.


Flight 프로토콜과 서버 측 프로토타입 오염

조금 더 기술적으로 들어가 보면, 취약점은 Flight 프로토콜 역직렬화 → 프로토타입 오염 → RCE라는 전형적인 흐름을 가진다.

1. 문제가 된 코드 패턴

문제가 된 함수의 개념적인 형태는 다음과 같다(개념화한 예).

// 취약 버전 (개념 코드)
export function requireModule<T>(metadata: ClientReference<T>): T {
  const moduleExports = parcelRequire(metadata[ID]); // 모듈 로딩
  return moduleExports[metadata[NAME]];              // 클라이언트 입력을 그대로 사용
}
  • metadata[ID]: 어떤 모듈을 로딩할지
  • metadata[NAME]: 그 모듈의 어떤 export에 접근할지

여기서 metadata[NAME]이 전혀 검증 없이 그대로 사용된 것이 문제다. 공격자가 이 값을 __proto__, constructor 같은 민감한 키로 바꾸면, 모듈 export 대신 객체 프로토타입 체인에 손을 댈 수 있는 길이 열린다.

2. 프로토타입 오염 → RCE

  • 서버 코드 어딘가에서 “평범한 객체 생성”을 할 때,

  • 이미 오염된 Object.prototype의 속성들이 따라붙는다.

  • 그 안에 spawnSync('sh') 같은 셸 실행 로직이 연결되어 있으면, 새로 생성되는 객체를 경유해 코드가 실행될 수 있다.
  • 이렇게 해서 공격자는 단 한 번의 HTTP 요청으로도 원격 코드 실행에 도달할 수 있다.

실제로 무엇을 패치했는가

React 측 패치의 핵심은 “클라이언트가 보낸 값을 그대로 믿지 않도록 강제”하는 것이다.

대표적인 변경이 바로 requireModule 함수다.

// 패치 후 (개념 코드)
export function requireModule<T>(metadata: ClientReference<T>): T {
  const moduleExports = parcelRequire(metadata[ID]);

  // 👇 클라이언트가 보낸 NAME이 실제 own property인지 확인
  if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
    return moduleExports[metadata[NAME]];
  }

  return undefined as any;
}

이 한 줄로 인해:

  • __proto__, constructor 등 프로토타입 체인을 건드리는 키는 own property가 아니기 때문에 접근이 차단된다.
  • 결과적으로, 프로토타입 오염을 RCE로 연결하는 핵심 공격 벡터가 막힌다.

실제 패치에는 이외에도:

  • Flight 페이로드 구조 검증 강화
  • 특정 위험한 필드의 사용 제한
  • 관련 코드 경로에 대한 추가 방어 로직

등이 포함된 것으로 알려져 있다. 하지만 큰 그림에서 보면 위와 같은 “검증 추가”가 핵심이다.


Next.js가 React를 서버로 확장하면서 생긴 구조적 충돌

원래 React는 “브라우저에서 돌아가는 UI 라이브러리”였다. 그러나 RSC와 Server Actions가 등장하면서, React는 더 이상 브라우저 전용이 아니다.

Next.js는 이 기능들을 통합하여:

  • 클라이언트 컴포넌트 + 서버 컴포넌트 + 서버 액션을 하나의 앱처럼 보이게 만들었다.
  • 그 대가로, 클라이언트가 보낸 데이터가 서버 모듈 로딩/코드 실행 경로를 직접 건드릴 수 있는 구조가 되었다.

여기서 보안 모델이 어긋났다.

프론트엔드 가정을 서버로 가져온 문제

프론트엔드 환경에서는 보통:

  • 코드 번들이 정적이고 불변이며,
  • 모듈 로딩은 빌드 시점에 결정되어 있고,
  • 런타임에 사용자가 모듈 이름이나 export 이름을 바꾸는 일은 거의 없다.

그래서,

moduleExports[userInput]

같은 패턴이 상대적으로 덜 위험하게 느껴질 수 있다.

하지만 서버에서는 이야기가 완전히 다르다.

  • 입력은 항상 신뢰할 수 없는 네트워크 데이터이고,
  • 모듈 로딩/객체 생성은 곧 서버 자원·파일·프로세스 접근과 직결된다.
  • 이런 환경에서 역직렬화/다이내믹 로딩에 사용자 입력을 섞으면, 항상 RCE 후보가 된다.

필요했던 것은 다음과 같은 “새로운 보안 모델”이었다.

  • Flight 페이로드 구조에 대한 엄격한 스키마 검증
  • 클라이언트가 제어 가능한 값에 대한 화이트리스트 기반 접근
  • HMAC 같은 요청 서명/무결성 검증
  • 서버 측에서 “RSC는 항상 신뢰할 수 없는 입력을 받는다”는 가정 하에 설계

그러나 실제 구현에서는 전통적인 React(프론트 전용) 신뢰 모델이 서버 영역까지 그대로 끌려와, 이번과 같은 구조적 취약점이 드러났다.


결론: 프론트엔드와 서버 경계가 흐려질수록, 보안 모델부터 다시 설계해야 한다

이번 React/Next.js 취약점은 단순한 “버그 하나”라기보다는:

  • 프론트엔드 라이브러리였던 React가 서버 실행 경로까지 포괄하는 순간,
  • 기존의 “프론트엔드 중심 신뢰 모델”이 더 이상 통하지 않게 되었다는 것을 보여준다.

전통적인 React + DRF/FastAPI처럼:

  • “브라우저에서만 React가 돌고”
  • “서버는 HTTP API만 제공하는” 아키텍처에서는 이번과 같은 취약점이 설 자리가 없다.

반대로, Next.js처럼:

  • 서버 실행 경로에 React 직렬화/역직렬화 프로토콜이 깊게 엮인 구조에서는
  • 기능과 DX 향상만큼이나 새로운 보안 모델과 검증 체계를 함께 설계해야 한다.

앞으로도 “프론트엔드-백엔드 경계가 사라지는” 프레임워크는 계속 나올 것이다. 이번 사건은 그럴수록 편의성 못지않게, 보안 경계와 신뢰 모델을 제일 먼저 설계해야 한다는 경고라고 볼 수 있다.