개발

내 Next.js 서버가 3일간 남의 코인을 캐고 있었다: React2Shell 침해 대응 회고

owel.dev 2026. 10. 8. 02:00

Next.js 서버가 뚫려 약 3일 동안 암호화폐 채굴에 쓰였습니다. 침입 경로는 React2Shell 취약점(CVE-2025-55182)으로 추정됩니다.

이 글은 이상 징후를 발견한 순간부터 침입 경로를 추적하고 재발 방지책을 적용하기까지의 기록입니다.

배경

Next.js 애플리케이션을 AWS EC2에서 pm2로 실행해 운영하고 있었습니다. 그런데 어느 날부터 평소 1초 정도 걸리던 API 응답이 10초 정도로 느려졌습니다.

원인 파악

응답이 느려진 원익을 파악하기 위해 EC2의 자원 사용률을 조회해보니, 평소 5~10% 였던 CPU 사용률이 3일 전부터 99%를 유지하고 있었습니다. top 명령으로 프로세스 현황을 살펴보니 처음 보는 이름의 프로세스가 CPU 대부분을 점유하고 있었습니다.


프로세스 이름을 AI에게 물어보니 암호화폐 채굴 프로그램이라는 답을 받았습니다. 서버가 침해당했다는 것을 그때 알았습니다. 우선 채굴 프로세스들을 종료하고, 피해가 어디까지 미쳤는지와 어떤 조치가 필요한지 점검하기 시작했습니다.

피해 범위 점검

서버에는 S3에 요청을 보내기 위한 AWS IAM 엑세스 키와 AI API를 사용하기 위한 OpenAI API 키가 있었습니다. 서버가 침해당한 이상 이 키들도 유출당했다고 봐야 했으므로, 두 키를 모두 폐기했습니다.

 

다음으로 DB에 개인정보나 민감한 정보가 저장되어 있는지 점검했습니다. 다행히 그런 정보는 없었지만, DB 접속 정보 역시 서버에 있었으므로 DBMS 계정의 비밀번호를 변경했습니다.

침입 경로 추적

가장 먼저 의심한 것은 SSH 키 탈취였습니다. /var/log/secure 에서 SSH 접속 기록을 찾아봤지만, 저 외에 누군가 접속한 기록은 없었습니다. 다만 관리자 권한을 얻은 공격자라면 로그를 지웠을 수도 있으므로, 이 기록만으로 단정할 수는 없었습니다.

 

그래서 키가 유출될 가능성 자체를 따져 봤습니다. SSH 키는 노트북 디스크에만 보관했고, 어딘가에 공유하거나 기록한 적이 없었습니다. 키가 유출되려면 노트북 자체가 침해당해야 하는데, 그럴 가능성은 매우 낮다고 보고 후보에서 제외했습니다.

 

단서는 pm2로 실행 중이던 애플리케이션 디렉토리에서 나왔습니다. 제가 만든 적 없는 정체불명의 파일들이 생성되어 있었습니다. 이 디렉토리는 Next.js 프로세스의 작업 디렉토리이므로, 여기에 파일이 생겼다면 Next.js 프로세스가 누군가의 명령을 실행했을 가능성이 있습니다. 얼마 전 크게 화제가 된 React2Shell 취약점이 떠올랐습니다.

 

이 취약점을 이용하면 공격자가 보낸 명령을 Next.js 프로세스가 대신 실행하게 됩니다. 공격자가 인터넷에 공개된 서버들을 훑으며 취약점이 있는지 시험해 보려고 파일 생성 명령을 보냈고, 그 결과로 이 디렉토리에 파일이 생긴 것이라고 추측했습니다.

 

이 추측을 뒷받침하는 근거도 확인했습니다. npm audit으로 검사해 보니 서버의 Next.js는 해당 취약점이 패치되지 않은 버전이었습니다. 또한 채굴기 설치는 이 취약점을 악용한 공격에서 실제로 여러 차례 관찰된 피해 양상이었습니다. (Google Threat Intelligence 보고서)

 

 

이를 근거로 React2Shell을 가장 유력한 침입 경로로 판단했습니다.

React2Shell이란?

React2Shell은 2025년 12월 3일 공개된 React Server Components(RSC)의 원격 코드 실행 취약점 입니다. CVE 번호는 CVE-2025-55182이고, CVSS 점수는 최고점인 10.0입니다.(React 공식 블로그)

유형 인증이 필요 없는 원격 코드 실행(RCE)
취약 패키지 react-server-dom-webpack, -parcel, -turbopack 19.0, 19.1.0, 19.1.1, 19.2.0
영향받는 Next.js 버전 App Router를 쓰는 15.x, 16.x, 그리고 14.3.0-canary.77 이후 canary
해결 방법 패치 버전으로 업그레이드 (우회책 없음)

원리

React Server Functions는 클라이언트가 서버의 함수를 호출할 수 있게 해주는 RSC의 기능입니다. 클라이언트의 호출은 HTTP 요청으로 서버에 전달되고, 서버의 React가 요청 본문을 역직렬화해 함수 호출로 바꿉니다.

 

문제는 이 역직렬화 과정에 있었습니다. 공격자가 조작된 HTTP 요청 하나를 보내면, 로그인 없이도 서버에서 임의의 코드를 실행할 수 있었습니다. Next.js는 App Router에서 RSC를 사용하기 때문에 이 취약점의 영향을 그대로 받았습니다. 

서버 전체를 탈취당한 이유

pm2로 띄운 프로세스는 pm2를 실행한 OS 계정의 권한을 그대로 물려받습니다. 따라서 pm2로 배포한 Next.js 애플리케이션에서 원격 코드 실행 공격을 당하면, 공격자는 그 계정으로 서버의 Shell을 쓰는 것과 같은 권한을 얻습니다.

 

게다가 EC2의 기본 계정(ec2-user, ubuntu 등)은 비밀번호 없이 sudo를 사용할 수 있습니다. 저는 기본 계정으로 pm2를 실행하고 있었으므로, root 권한까지 넘어갔다고 봐야 했습니다.

 

공격은 다음과 같은 순서로 진행됐을 것입니다.

  1. 공격자가 인터넷을 스캔해 외부에 공개된 Next.js 서버를 찾습니다.
  2. 찾아낸 서버에 악의적으로 조작한 HTTP 요청을 보냅니다.
  3. React가 요청을 역직렬화하는 과정에서 공격자의 코드가 실행됩니다.
  4. 그 코드가 pm2를 실행한 계정의 권한으로 동작합니다.
  5. 공격자는 애플리케이션 디렉토리에 스크립트 파일을 내려받고, 그 스크립트로 채굴 프로세스를 실행합니다.

root 권한을 가진 공격자는 서버의 파일과 환경 변수를 모두 읽을 수 있고, 그 안의 접속 정보로 DB에도 접근할 수 있습니다. 앞에서 서버에 있던 API 키들을 모두 폐기하고, DB에 유출되면 안 되는 정보가 있는지 점검해야 했던 이유입니다.

사후 예방 조치

우선 Next.js를 보안 패치가 반영된 버전으로 업그레이드했습니다. 그리고 이번처럼 보안 패치를 놓치는 일이 없도록, 현업에서는 어떤 방법을 쓰는지 알아봤습니다.

현업에서 자주 쓰는 방법

방법 대표 도구 하는 일
의존성 취약점 알림과 자동 PR GitHub Dependabot, Renovate 취약한 의존성이 발견되면 알림을 보내고, 패치 버전으로 올리는 PR을 자동으로 생성
CI 취약점 검사 npm audit, OSV-Scanner, Snyk, Trivy 빌드 단계에서 알려진 취약점을 검사하고, 기준 이상이면 배포를 중단
보안 공지 구독 GitHub 저장소의 Security alerts, 프레임워크 공식 블로그, KISA 보호나라 사용하는 프레임워크의 보안 공지를 직접 받아 봄
실행 환경 스캔 Amazon Inspector, 컨테이너 이미지 스캔 실제 배포된 서버와 이미지에 설치된 패키지의 취약점을 지속적으로 검사
WAF 가상 패치 AWS WAF 관리형 규칙, Cloudflare WAF 알려진 공격 요청을 서버 앞단에서 차단해 패치 전까지 시간을 확보
이상 징후 탐지 CloudWatch 경보, Amazon GuardDuty CPU 급등이나 채굴 관련 통신 같은 침해 징후를 알림

 

위의 네 가지는 패치가 필요한 취약점을 놓치지 않기 위한 방법이고, 아래 두 가지는 패치가 늦었을 때 피해를 줄이기 위한 방법입니다.

이 프로젝트에 적용한 것

이 중 별도의 비용이나 인프라 없이 바로 적용할 수 있는 두 가지를 적용했습니다.

  1. Dependabot 알림과 보안 업데이트를 켰습니다. 취약한 의존성이 발견되면 이메일로 알림을 받고, 패치된 버전으로 올리는 PR이 자동으로 등록됩니다.
  2. 배포 파이프라인에 npm audit 단계를 추가했습니다. high 등급 이상의 취약점이 있으면 배포가 중단됩니다.
npm audit --omit=dev --audit-level=high

마치며

이번 일로 공격은 예상치 못한 경로로 들어올 수 있고, 한 번 당하면 그 영향력이 엄청나다는 것을 배웠습니다.

 

처음에는 SSH 키 탈취를 의심했지만, 유력한 침입 경로는 오랫동안 문제없이 사용하던 프레임워크의 취약점이었습니다.

그리고 HTTP 요청 하나로 서버에서 공격자의 명령이 실행되어 서버 자원이 채굴에 쓰이고 API 키와 DB까지 위험에 노출되었습니다.

 

배포하기 전에 아래와 같은 질문을 미리 던져 봤더라면, 침입을 막거나 적어도 피해를 줄일 수 있었을 것입니다.

미리 던졌어야 할 질문 이번 사고에서의 답 앞으로의 원칙
이 프로세스가 침해당면 공격자는 어떤 권한을 얻는가? pm2를 실행한 계정의 권한 전부 권한을 최소로 줄이고 격리한다
서버에 있는 키로 무엇을 할 수 있는가? AWS 키와 OpenAI API 키를 그대로 쓸 수 있음 서버에 두는 키와 그 권한을 최소로 한다
침입을 어떻게 알아차리는가? CPU 99%가 3일 이어졌고, API가 느려진 뒤에야 알았음 자원 사용률에 경보를 건다
보안 패치는 어떻게 챙기는가? 화제가 된 취약점인 줄 알면서도 패치하지 않은 상태였음 알림과 검사를 자동화한다

 

평소 보안을 꾸준히 공부하고, 서비스를 배포하기 전에는 침해당하는 상황을 미리 가정해 대비해야 한다고 생각하게 되었습니다.