이 글은 개인 블로그에 최초로 업로드 되었습니다. (해당 글 가기)
2025년 12월 3일, 팀원이 전해 준 소식으로 React 서버 컴포넌트의 보안 이슈를 접하게 됐습니다. 보안 취약점 점수 10.0 만점으로, 반드시 즉시 대응이 필요한 취약점이라는 사실에 놀랐습니다.
핵심은 '인증 없는 원격 코드 실행(RCE)'이 가능하다는 것이었습니다. 처음에는 의문이 들었습니다. "우리 프로젝트는 Server Actions를 적극적으로 사용하지 않는데, 그래도 위험한가?"
결론부터 말씀드리자면, 위험합니다. `Server Actions`를 직접 구현하지 않았더라도, Next.js의 App Router와 같이 RSC를 지원하는 프레임워크를 사용하고 있다면 공격에 노출되어 있습니다.
RSC에 무슨 문제가 있었던 것일까요? 이번 포스팅에서는 CVE-2025-55182 취약점의 기술적 원인과 해결 방안을 살펴보려 합니다.
-----
1. 취약점의 핵심: 너무 관대한 '직렬화' 프로토콜
이 이슈는 리액트가 서버와 클라이언트 사이에서 데이터를 주고받는 방식과 연관돼 있습니다.
서버가 클라이언트에 HTTP 프로토콜로 컴포넌트를 전송할 때 데이터를 직렬화하고 이를 클라이언트에서 역직렬화해서 사용하는 것처럼, 클라이언트가 서버에 보낸 요청을 해석할 때도 직렬화/역직렬화가 발생합니다. 문제는 이 역직렬화 과정에서 서버가 클라이언트 요청을 지나치게 신뢰했다는 것입니다.
클라이언트가 보낸 요청이 실제로 '공개된 서버 액션'인지 검증하는 절차가 부족했고, 공격자가 요청을 조작하여 서버 내부의 민감한 정보를 빼낼 수 있는 환경이 마련된 것입니다.
-----
2. 리액트 팀은 이 문제를 어떻게 해결했을까
리액트 팀이 실제로 이 문제를 수정한 코드를 살펴보면 취약점이 무엇인지 더 명확해집니다.
Before
수정 전의 코드는 클라이언트가 요청한 모듈이 명시적으로 export된 것이 아니라도 접근할 수 있었습니다.
// react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js (수정 전, 단순화됨)
export function requireModule<T>(metadata: ClientReference<T>): T {
//...
// 검증 없이 프로퍼티에 접근
return moduleExports[metadata[NAME]];
}
코드를 살펴보면, 접근하려는 속성이 해당 함수뿐 아니라 프로토타입 체인에 존재하는 어떤 속성이든 접근이 가능합니다. 즉, 공격자가 `__proto__`를 통해 프로토타입 체인을 타고 `Object.prototype.constructor`를 통해 `Function`을 접근할 수 있습니다.
함수에 얼마든 원하는 메서드나 프로퍼티를 추가해 정보를 빼내는 데 사용할 수 있는 것입니다.
After
패치된 버전에서는 이용 가능하지 않은 속성에 접근하지 못하도록 검증이 추가되었습니다.
// react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js (수정 후)
export function requireModule<T>(metadata: ClientReference<T>): T {
// ...
// 상속된 속성에 접근하지 못하도록 hasOwnProperty로 검증
if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
return moduleExports[metadata[NAME]];
}
}
-----
3. 왜 RSC는 이런 구조를 가졌을까?
이 이슈와 관련한 토론이 벌어지는 것을 보면서 "애초에 리액트는 왜 이렇게 복잡하고 위험한 구조에 이르게 되었을까?"라는 의문이 들었습니다.
즉, 서버에서 실행되는 컴포넌트를 만든다는 것은 곧 이번 사건처럼 자칫하면 시스템을 마비시키거나 서비스 이용자 모두의 개인정보를 빼낼 수 있는 보안 취약점이 생길 수도 있다는 것일 텐데, 왜 이런 기능을 도입하게 되었을까? 위험을 감수하고서라도 누릴 수 있는 RSC의 이점은 무엇이었을까? 하는 것이죠.
기존의 CSR 환경에서는 브라우저와 서버가 철저히 분리되어 있고 API를 통해서만 대화할 수 있었습니다. 보안 경계는 명확했지만, 갈수록 많은 사용자 인터랙션이 요구되면서 번들 사이즈가 커지고 이것이 클라이언트에 큰 부담을 주는 수준이 되기도 했습니다.ƒ
이에, RSC는 서버와 클라이언트의 경계를 허물어 "서버 로직을 컴포넌트 안으로" 가져오는 것을 목표로 했습니다. 이를 위해 마치 로컬 함수를 호출하듯 서버 함수를 호출하는 RPC(Remote Procedure Call) 방식을 차용했습니다.
* 장점: 개발자는 네트워크 요청 코드를 일일이 짤 필요가 없습니다. 그냥 함수를 `import` 해서 쓰면 됩니다.
* 단점(위험): 네트워크 레이어가 추상화되어 숨겨지다 보니, 개발자는 이 함수 호출이 '공개된 인터넷을 통하는 HTTP 요청'이라는 사실을 망각하기 쉽습니다.
이번 취약점은 그 '편리하게 숨겨진 통신 레이어'의 틈새가 벌어진 사건입니다.
-----
4. 이번 사건에 대한 대응과 새롭게 알게 된 점
보안 경고에 대한 조치
소식을 접한 즉시 프로젝트의 의존성을 확인하고 업데이트를 진행했습니다.
1. 의존성 확인: React와 Next.js의 취약한 버전(리액트: 19.0.0 ~ 19.2.0, Next.js: 15, 16 전 버전, Next.js 14.3.0-canary.77 이후 버전)을 사용 중인지 확인
2. 버전 업데이트: React 19.2.1, Next.js의 패치된 버전(https://nextjs.org/blog/CVE-2025-66478)으로 업데이트
3. 배포: 패치된 버전으로 다시 빌드하고 배포했습니다.
프론트엔드 개발자의 변화된 역할
이번 사건을 통해 프론트엔드 개발자가 신경 써야 할 보안 이슈들이 생각보다 넓다는 것을 깨닫게 되었습니다.
지금까지는 XSS 같은 브라우저 단의 보안 이슈만 신경 쓰면 된다고 생각했지만, 갈수록 Next.js가 프론트엔드의 표준이 되어가는 지금, 프론트엔드 개발자의 영역은 이미 서버 깊숙한 곳까지 들어와 있습니다.
내가 작성하는 코드가 서버에서 실행되고, 데이터베이스에 직접 접근하기도 한다는 점을 명확히 인지하고, 그에 따른 책임에 대해서도 생각해 봐야겠다는 생각이 들었습니다.
또한 서버 컴포넌트가 어떤 맥락에서 도입되었고, 어디서 어떻게 실행되며 무슨 일을 하는지 정확히 이해하는 것이 필수라는 점 또한 되새기는 계기가 되었습니다.
-----
참고 자료:
- https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components
- https://github.com/facebook/react/commit/7dc903c
- https://gomguk.tistory.com/306
'React' 카테고리의 다른 글
| [React] 리액트 버전 18의 주요 특징들 (0) | 2023.10.10 |
|---|---|
| client-side routing (0) | 2023.10.09 |
| [React] 리액트 앱에 인증 추가하기 (2) | 2023.05.01 |
| [React] 리액트 라우터 data api 사용하기 (0) | 2023.04.26 |
| [Redux] 비동기 작업 처리하기(useEffect, thunk) (0) | 2023.04.21 |