React2Shell : Anatomie technique d’une chaîne d’exploitation de l’UI au serveur
Dans l'imaginaire collectif, le front-end est souvent perçu comme un "bac à sable" : une faille y est grave pour l'utilisateur, mais inoffensive pour l'infrastructure. Cependant, l'avènement du Server-Side Rendering (SSR) et des frameworks full-stack comme Next.js ou Remix a radicalement changé la donne.
Aujourd'hui, nous décryptons le concept de React2Shell : comment une simple injection de contenu dans une interface React peut aboutir à une prise de contrôle totale du serveur (RCE).
Le mythe du « front-end inoffensif »
L'évolution des architectures web a effacé la frontière physique entre le client et le serveur. Avec le SSR, le code JavaScript qui génère l'interface s'exécute sur vos serveurs de production. Cette "isomorphisme" signifie qu'une donnée malicieuse injectée côté client peut être traitée par le moteur Node.js du serveur. Le concept React2Shell n'est pas une vulnérabilité isolée, c'est une chaîne d’exploitation.
Définition technique de React2Shell
React2Shell désigne l'escalade d'une vulnérabilité front-end (souvent de type XSS) vers une exécution de commandes système (Remote Code Execution) via le contexte du rendu serveur.
Ce que c'est : Une exploitation combinant injection logique et mauvaise isolation SSR.
Ce que ce n'est pas : Une faille native de React, mais une erreur d'implémentation.
Architecture type vulnérable
Pour qu'une attaque soit possible, l'application présente généralement ce flux :
Entrée : Un utilisateur soumet une donnée (profil, formule, commentaire).
Stockage : La donnée est stockée sans nettoyage suffisant.
SSR : Le serveur Node.js récupère cette donnée et l'injecte dans un composant React lors du rendu de la page.
Le point d'entrée : injection côté React
Bien que React protège contre l'injection via l'échappement automatique, des fonctions comme dangerouslySetInnerHTML ou l'utilisation de données utilisateur pour définir des propriétés dynamiques créent des brèches. Si un attaquant injecte une logique capable d'influencer le rendu, l'escalade peut commencer.
Escalade via le Server-Side Rendering (SSR)
C'est le cœur technique de l'attaque. Si le composant vulnérable utilise des fonctions de logique dynamique basées sur les données injectées, l'attaquant sort du contexte "UI" pour entrer dans le contexte "Runtime Node.js".
Cas concret : Le moteur de calcul personnalisé
Imaginons une application qui permet à l'utilisateur de personnaliser des calculs via une "formule".
❌ Exemple de Code Vulnérable
Ici, le développeur utilise eval() pour traiter la formule côté serveur.
// pages/dashboard.js (Next.js - SSR)
export async function getServerSideProps(context) {
// Récupération d'une "formule" enregistrée par l'utilisateur
// Exemple légitime : "data.price * 1.2"
const { formula } = await fetchUserPrefs(context.query.id);
return { props: { formula, data: { price: 100 } } };
}
export default function Dashboard({ formula, data }) {
const calculate = (input) => {
try {
// DANGER : s'exécute sur le serveur pendant le SSR
return eval(formula.replace('data', JSON.stringify(input)));
} catch (e) { return "Erreur"; }
};
return <div>Résultat : {calculate(data)}</div>;
}
L'Exploitation : Un attaquant injecte la formule suivante : (function(){ const exec = require('child_process').execSync; return exec('cat /etc/passwd').toString(); })() Le serveur exécute alors la commande système et affiche le contenu du fichier /etc/passwd directement dans la page HTML.
✅ Exemple de Code Sécurisé
Il faut proscrire l'évaluation de code et utiliser un parseur isolé avec une validation stricte.
import { evaluate } from 'mathjs'; // Parseur mathématique sécurisé
export async function getServerSideProps(context) {
const { formula } = await fetchUserPrefs(context.query.id);
// Validation stricte par Regex (uniquement chiffres et opérateurs)
const safePattern = /^[0-9a-zA-Z\s\+\-\*\/\.\(\)]+$/;
if (!safePattern.test(formula)) return { props: { formula: "0", error: "Invalide" } };
return { props: { formula, data: { price: 100 } } };
}
export default function Dashboard({ formula, data }) {
const calculate = (input) => {
try {
// Sécurité : mathjs ne peut pas accéder à 'require' ou 'process'
return evaluate(formula, { price: input.price });
} catch (e) { return "Calcul impossible"; }
};
return <div>Résultat : {calculate(data)}</div>;
}
De JavaScript à la commande système (RCE)
Une fois que l'attaquant peut exécuter du JavaScript sur Node.js, il utilise le module child_process.
exec(): Pour lancer des scripts shell.Impact : Exfiltration de variables d'environnement (clés API), lecture de secrets de base de données, ou rebond (pivot) sur le réseau interne.
Analyse de l'impact sécurité
Le risque est classé Critique. Contrairement à un XSS classique, React2Shell cible l'infrastructure. Les environnements les plus exposés sont ceux où les conteneurs Docker tournent avec des privilèges élevés (root).
Mesures de mitigation techniques
Développement : Supprimer
eval(), utiliser des bibliothèques de sanitization commeDOMPurifycôté serveur.Infrastructure : Utiliser des conteneurs non-privilégiés, mettre en place des politiques seccomp pour limiter les appels système autorisés.
Sécurité : Audit de code systématique sur les composants SSR manipulant des données "brutes".
React2Shell et le futur des attaques web
Avec l'arrivée des React Server Components, la distinction entre front et back devient encore plus ténue. La sécurité ne doit plus être pensée par "couches" mais par flux de données. Un octet non validé en entrée est une commande potentielle en sortie.
Conclusion
React2Shell est le symptôme d'une architecture moderne où le front-end a pris le contrôle du serveur. La sécurité front-end est une illusion si elle n'est pas intégrée à une vision globale du cycle de vie des données.

Commentaires
Enregistrer un commentaire