취약점
>=3.0.0nuxt<3.16.0에서 nuxt 서버 앞에 CDN이 있고 그 CDN이 URL 쿼리를 제외한 경로를 캐시에 저장할 때 http://mysite.com/?/_payload.json과 같이 쿼리 뒤에 _payload.json이 붙은 경로의 캐시 키가 http://mysite.com/로만 구성되어 다른 사용자가 http://mysite.com/에 요청을 보낼 때 캐시가 반환되어 정상적인 응답이 아닌 http://mysite.com/?/_payload.json의 응답이 반환되게 되는 취약점이다.
시나리오
1. http://mysite.com/?/_payload.json에 요청
2. CDN에 url query를 제외한 경로로 캐시키 생성 EX) http.mysite.com./
3. http://mysite.com/에 요청
4. CDN에 http://mysite.com/ 경로에 대한 캐시가 생성되어 있으므로 캐시에서 반환
5. http://mysite.com/?/_payload.json 페이지 반환
이를 통해 공격자가 캐시가 사라지는 주기마다 http://mysite.com/?/_payload.json와 같은 경로에 요청을 보내 캐시를 계속 생성시킨다면 다른 사용자가 페이지에 접속했을 때 정상적인 응답을 받을 수 없는 DoS 취약점이 발생할 수 있다.
https://nvd.nist.gov/vuln/detail/CVE-2025-27415
NVD - CVE-2025-27415
CVE-2025-27415 Detail Awaiting Analysis This CVE record has been marked for NVD enrichment efforts. Description Nuxt is an open-source web development framework for Vue.js. Prior to 3.16.0, by sending a crafted HTTP request to a server behind an CDN, it is
nvd.nist.gov
PoC
/** pages/index.vue */
<script setup>
definePageMeta({
payload: () => {
return { message: 'Hello payload' }
}
})
</script>
<template>
<h1>Hello from CVE-2025-27415</h1>
</template>
/** nginx.conf */
worker_processes 1;
events { worker_connections 1024; }
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
proxy_cache_path /tmp/nginx-cache levels=1:2 keys_zone=my_cache:10m inactive=30s use_temp_path=off;
server {
listen 80;
location / {
proxy_pass http://localhost:3000;
proxy_cache my_cache;
proxy_cache_valid 200 1m;
proxy_cache_key "$scheme$host$uri"; /** URL 쿼리를 제외한 경로가 캐시키로 저장됨 */
add_header X-Cache-Status $upstream_cache_status;
}
}
}
환경 세팅
nvm install 20
nvm use 20
mkdir CVE-2025-27415
cd CVE-2025-27415
npx nuxi@3.15.0 init my-app
mkdir nginx
cd nginx
vi nginx.conf
sudo nginx -c $(pwd)/nginx.conf
cd my-app
npm install
mkdir pages
cd pages
vi index.vue
cd my-app
npm run build
npm run start
/** package.json */
{
"name": "nuxt-app",
"private": true,
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
"start": "nuxt start"
},
"dependencies": {
"nuxt": "3.15.0",
"vue": "^3.4.21",
"vue-router": "^4.2.5"
}
}
github
https://github.com/jiseoung/CVE-2025-27415-PoC
GitHub - jiseoung/CVE-2025-27415-PoC: Nuxt3 Acceptance of Extraneous Untrusted Data With Trusted Data vulnerability
Nuxt3 Acceptance of Extraneous Untrusted Data With Trusted Data vulnerability - jiseoung/CVE-2025-27415-PoC
github.com
디버깅 환경
/** .vscode/launch.json */
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Nuxt Start: Debug Nitro",
"program": "${workspaceFolder}/.output/server/index.mjs",
"runtimeArgs": [
"--inspect-brk"
],
"cwd": "${workspaceFolder}",
"skipFiles": ["<node_internals>/**"],
"console": "integratedTerminal"
}
]
}
/* nuxt.config.tx **/
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: '2025-05-15',
devtools: { enabled: true },
sourcemap: {
server: true,
client: true,
},
})
npm run build
node --inspect-brk .output/server/index.mjs
/* --inspect-brk로 인해 첫줄에서 멈춤
npm run build로 인해 생성된 .output/server/index.mjs가 entry point **/
디버깅 방법
0. sudo nginx -c $(pwd)/nginx.conf (nginx 실행)
1. npm run build
2. node --inspect-brk .output/server/index.mjs
3. nitro.mjs 4886줄 중단점 (Nitro 서버 인스턴스 생성)
const nitroApp$1 = createNitroApp();
4. renderer.mjs 185줄 중단점 (_payload.json에 대한 요청인지 검증하는 코드의 시작점)
const PAYLOAD_URL_RE = /\/_payload.json(\?.*)?$/ ;
const isRenderingPayload = PAYLOAD_URL_RE.test(url) && !isRenderingIsland;
5. vscode Run and Debug 실행
6. http://localhost:8080/?/_payload.json으로 요청
7. http://localhost:8080/ 요청
여기서는 디버거에 잡히지 않는데 이것이 응답이 nginx cache에서 반환되었다는 증거
취약점 분석
nuxt3에서 서버를 빌드하고 실행할 때 봐야할 주요 파일이
,output/server/index.mjs
.output/server/chunks/nitro/nitro.mjs
.output/server/chunks/routes/renderer.mjs
이렇게 3개이다.
index.mjs가 entry point이고 nitro.mjs는 서버 실행, renderer.mjs는 페이지 렌더링을 하는 코드가 담겨 있다.
이 취약점은 페이지를 렌더링 하는 과정과 관련된 취약점이므로 renderer.mjs 파일을 보면 된다.
const componentIslands = false;
const PAYLOAD_URL_RE = /\/_payload.json(\?.*)?$/ ;


url이 '/?/_payload.json이므로 정규식에 매칭이 되고 isRenderingIsland의 값은 false이므로 isRederingPayload 변수는 true를 담게 된다.

그러므로 바로 아래에 있는 이 if문 내부가 실행되는데 각 변수가

이렇게 설정된다.

다음으로 더 아래인 234줄의 if문 내부가 실행되는데

SSR과정에 필요한 정보를 담는 ssrContext변수는 이렇게 설정되어 있고
renderPayloadResponse함수는

ssrContext변수를 인자로 페이지 재사용 시 필요한 정보를 반환한다.

그 후 캐시가 사라지기 전에 http://localhost:8080/로 요청을 보내면 디버거에 잡히지 않고

이렇게 응답이 반환되는데 이는 nginx의 캐시에서 반환된 응답이다.

http://localhost:8080/로 요청을 보냈을 때 원래 이렇게 index.vue 파일의 내용이 반환되어야 하는데 cache poisoning으로 인해 서로 다른 요청임에도 캐시에서 응답이 반환되었다.
취약점 원리
이 취약점은 nuxt3의 내부 라우팅 처리와 CDN 캐시키의 잘못된 설정이 얽혀 발생하는 취약점이다.
nuxt3에서 _payload.json으로 끝나는 URL에 요청을 보낼 시 위의 분석에서 본 것처럼 renderPayloadResponse(ssrContext) 함수를 사용해 SSR 페이지 요청과 분리되어 응답 처리가 이루어진다.
이때 /?/_payload.json으로 요청을 보낼 시 URL이 _payload.json으로 끝나므로 SSR 페이지 요청과 분리된 payload요청으로 처리하지만 CDN 캐시키를 uri까지만 저장되도록 해 ?뒤의 /_payload.json은 쿼리스트링의 키로 인식되어 /경로에 대한 캐시가 생성된다. 그러므로 클라이언트가 /경로에 대한 요청을 보냈을 때 CDN의 캐시가 반환되게 된다.
하지만 /_payload.json으로 요청을 보냈을 시에는 uri 자체가 /_payload.json이므로 /경로에 대한 캐시키 오염이 되지 않는 것이다.
_payload.json
nuxt3에서 SSR된 페이지를 클라이언트에서 다시 요청하면 클라이언트 단에서 그 페이지를 재사용(hydration)하게 된다.
이때 HTML만으로는 클라이언트가 페이지를 똑같이 만들어낼 수 없으므로 hook이나 state 등의 정보를 넘겨줘야 하는데 그 정보를 담고 있는 것이 _payload.json이다.
그래서 페이지를 재사용할 때 클라이언트가 _payload.json으로 요청을 보내 정보를 가져온 후 페이지를 만들어낸다.
EX) /test에 대한 요청에서는 /test/_payload.json으로 요청을 보낸다.
취약점 패치
/?/_payload.json으로 요청을 보냈을 때

먼저 _payload.json으로 요청을 보내는 것인지 검증하는 정규식이 바뀌었다.
[^?]*이 추가됨으로써 쿼리스트링의 시작을 나타내는 '?' 앞이 _payload.json으로 끝나는지 검증한다.

취약한 버전에서는 url 전체를 검증하는 반면 패치된 버전에서는 ssrContext.url을 검증하는데

ssrContext는 createSSRContext함수의 반환값이고
createSSRContext함수를 보면

ssrContext.url = event.path이므로 '/?/_payload.json'으로 요청이 들어와도 path인 '/'가 검증된다.

디버거의 Watch에서 변수를 확인해보면 '/?/_payload.json'으로 요청했음에도 ssrContext.url에는 '/'가 들어가 있다.
그래서 isRederingPayload의 값을 확인해보면

false가 들어가 있어 _payload.json이 반환되지 않고

renderHTMLDocument함수가 실행되어 '/'에 대한 SSR html이 반환된다.
'project' 카테고리의 다른 글
| axios Prototype Pollution 1 day 취약점 분석 (0) | 2025.07.23 |
|---|---|
| CVE-2025-26923 (0) | 2025.07.02 |
| CVE-2024-34343 1 day 취약점 분석 (0) | 2025.07.02 |
| Nodejs로 게시판을 만들면서 찾아봤던 것들[1] (0) | 2024.11.05 |