- Published on
React2Shell 취약점 경험기
- Authors

- Name
- Shin, Byungjin
개요
2025년은 유독 정보보안 사고가 많았던 한 해였다. 통신사, 정부기관, 유통 대기업 등 다양한 업종에서 보안 사고가 끊이지 않았고, 심지어 보안 전문 기업이 해킹을 당하는 일까지 있었다. 이런 뉴스를 계속 접하다 보니 자연스럽게 정보보안에 관심을 갖게 되었고, 노말틱 채널 같은 보안 관련 콘텐츠도 챙겨보게 되었다. 그런데 보안 사고라는 것이 뉴스 속 남의 일이 아니라, 내가 직접 경험하게 될 줄은 몰랐다.
이 글은 회사에서 사내용 LLM 서비스를 개발하던 중 React2Shell(CVE-2025-55182) 취약점으로 인해 실제 침해 시도를 겪고, 그 과정에서 배운 것들을 정리한 기록이다.
React2Shell, CVSS 10.0 만점의 초고위험 취약점
2025년 12월 3일, Meta와 Vercel이 React Server Components(RSC)의 Flight 프로토콜에 존재하는 unsafe deserialization 취약점을 공개했다. CVE-2025-55182로 등록된 이 취약점은 CVSS 10.0 만점을 받았고, 보안 커뮤니티는 2021년의 Log4Shell에 빗대어 이 취약점에 React2Shell이라는 이름을 붙였다.
CVSS 10.0이 어느 정도인지 감이 안 올 수 있는데, NVD(National Vulnerability Database)에 등록된 수십만 건의 취약점 중에서도 만점은 1% 미만에 해당하는 극히 드문 수치이다. 인증 없이 단 한 번의 HTTP POST 요청만으로 서버에서 임의 코드를 실행할 수 있는 치명적인 취약점이었다.
여기서 "임의 코드 실행"은 코드 한 줄 돌리고 끝나는 수준이 아니다. 처음에는 웹 서버 프로세스 권한으로 실행되지만, 실제 공격에서는 base64 | sudo -i 같은 파이프 인젝션으로 셸 제한을 우회해 곧장 root 권한까지 올라간 사례가 나왔다. 권한을 손에 쥔 공격자는 클라우드 인스턴스 메타데이터 서비스(169.254.169.254)에서 IAM 자격증명을 빼내고, 환경 변수에 담긴 API 키·DB 접속 정보를 긁어모으며, .ssh·.aws·.kube 같은 민감 디렉터리를 뒤졌다. 서버 한 대의 장악을 발판 삼아 클라우드 계정과 내부망 전체로 피해를 넓힌 것이다. 실제로 이 취약점은 CISA의 악용된 취약점 목록(KEV)에도 올랐다.
엄밀히 말하면 이 사건에는 두 개의 CVE가 얽혀 있다. CVE-2025-55182는 React Server Components를 구현한 React 코어 패키지(react-server-dom-webpack/-parcel/-turbopack, React 19.0·19.1·19.2) 자체의 결함이다. 처음에는 이를 의존하는 Next.js(App Router) 쪽에도 CVE-2025-66478이라는 별도 번호가 붙었지만, 곧 같은 결함임이 드러나면서 55182의 중복으로 기각(rejected)됐다. 즉 React2Shell이라는 이름은 이 둘을 아울러 가리키며, React를 직접 쓰든 Next.js를 거쳐 쓰든 취약한 버전의 RSC를 사용하면 동일한 위험에 노출된 셈이다.
이 취약점은 React 19.x 버전의 Server Components를 사용하는 경우, 특히 Next.js의 App Router 기반 애플리케이션에 해당한다. 공개 직후 중국 국가 배후 위협 그룹을 포함한 다수의 공격자가 수 시간 내에 익스플로잇을 시도했고, 주로 코인 마이너 설치, 리버스 셸 연결, 웹셸 배포를 노렸다. 우리 서버의 경우에는 코인 마이너가 설치되었고, 국적은 러시아였다.
당시 개발 상황
당시 나는 회사에서 사내용 LLM 서비스를 개발하고 있었다. 사내에 개발이 가능한 인력이 매우 제한되었고, 인프라를 담당하는 별도의 인력도 존재하지 않았다. 서비스 개발 일정이 촉박하여 모든 것을 신경 쓰며 진행할 수는 없었다. 처음 이 취약점에 대해 들었을 때 우리 서버도 위험할 수 있겠다고 생각했다. 하지만 현실적으로 Reverse Proxy 설정, CORS, SSRF 방어 같은 네트워크·보안 설정을 직접 할 수 있는 상황이 아니었다. 결국 서비스는 다음과 같은 상태로 열린 채 운영됐다.
- CORS: 모든 오리진 허용 (
*) - 방화벽/보안 그룹: 인바운드 포트 전체 개방
- 인증: 개발 편의를 위해 인증 미적용 상태로 운영
"아직 목업 수준이고, 사내 테스트일 뿐이니까 나중에 정리하자"는 생각이었다. 지금 돌이켜보면, 이 안일한 판단이 문제의 시작이었다.
침해, 그리고 서버 재구성
어느 날부터 서버가 반복적으로 죽기 시작했다. 처음에는 단순한 메모리 부족이나 애플리케이션 버그라고 생각했지만, 서버 로그를 자세히 살펴보니 상황은 달랐다.
- 출처를 알 수 없는 외부 IP에서 비정상적인 요청이 반복적으로 들어왔다. 루트 권한을 탈취하려는 시도로 보였다.
- AWS CloudWatch에서도 CPU 사용량이 비정상적으로 치솟는 패턴이 잡혔다.
시스템 로그를 확인해보니 공격자는 이미 루트 권한을 탈취한 상태였다. 앞서 설치된 코인 마이너도 이렇게 확보한 권한으로 심어진 것이었고, CPU 급증이 그 흔적이었다. 루트 계정의 자격 정보만 바꿔 막아보려 했지만, 권한을 다시 가져가는 스크립트가 이미 심겨 있어 소용이 없었다. 결국 서버를 새로 구성하는 수밖에 없었다.
대응과 조치
침해 징후를 인지했음에도 즉시 대응하기 어려운 현실적인 문제가 있었다. 당시 서비스는 오픈소스(OSS) 의존도가 매우 높았다. Dify, Langfuse 등 여러 OSS를 기반으로 구축되어 있었고, 각 시스템과의 연동을 위해 소스 코드를 직접 수정한 부분이 상당했다. 문제는 이런 커스텀 수정 사항을 upstream의 새로운 버전과 병합하는 프로세스가 전혀 준비되어 있지 않았다는 것이다. 솔직히 말하면, 이렇게 빠른 시점에 긴급 버전 업그레이드가 필요한 상황이 올 거라고 예상하지 못했다. 모든 것이 급하게 돌아가던 시기였기에, 업데이트 전략 같은 것을 세울 여유가 없었다. 이 일을 계기로 CI/CD 배포 프로세스를 강화했고, AWS 인프라를 콘솔에서 직접 만지는 대신 IaC(Infrastructure as Code) 도구로 자동화했다. IaC는 docker compose와 닮은 구석이 있어 이해가 빨랐고, 변경 이력도 git으로 손쉽게 추적할 수 있었다. 덕분에 OSI 7계층과 AWS 서비스가 정확히 대응하지는 않더라도, 계층별로 보안 대책을 최대한 갖추도록 보완할 수 있었다.
이 경험에서 배운 것
- 파급력이 큰 취약점은 직접 쓰든 간접적으로 의존하든 항상 모니터링해야 한다.
- OSS를 쓴다면 거기서 발생하는 이슈를 모니터링하고, 변경된 소스를 파악해 조치가 필요한 부분을 가려내고 실제로 반영하는 과정을 어느 정도 자동화할 필요가 있다.
- 인력이 부족하다고 손을 놓기보다는, 일정 부분은 AI Agent를 활용해 자동화해야 한다.
- 무엇보다 작은 이상도 무시하지 말고, 발견한 징후는 반드시 원인까지 확인해야 한다.