RCE (Remote Code Execution) : fonctionnement, techniques d’exploitation et prévention

RCE (Remote Code Execution) : fonctionnement, techniques d’exploitation et prévention

Les vulnérabilités RCE (Remote Code Execution) comptent parmi les failles de sécurité les plus dangereuses affectant les applications et infrastructures modernes. Leur capacité à permettre l’exécution de code arbitraire à distance en fait un point d’entrée récurrent dans certaines des cyberattaques les plus marquantes.

Dans cet article, nous détaillons le fonctionnement des RCE, les principales vulnérabilités susceptibles d’y conduire et les techniques utilisées lors d’un pentest pour les identifier et les exploiter. Nous explorons également les possibilités offertes à un attaquant après l’obtention d’une RCE avant de présenter les principales mesures permettant de prévenir, contenir et détecter ce type de compromission.

Guide complet sur les RCE (Remote Code Execution)

Qu’est-ce qu’une RCE (Remote Code Execution) ?

Une RCE désigne une situation dans laquelle un attaquant est capable de provoquer à distance l’exécution d’instructions non prévues sur un système cible.

Ces instructions peuvent prendre différentes formes. Dans certains cas, l’attaquant contrôle directement une commande exécutée par le système d’exploitation. Dans d’autres, il injecte du code dans un interpréteur, détourne un moteur de template, déclenche une chaîne de gadgets lors d’une désérialisation ou exploite une vulnérabilité présente dans un composant logiciel.

Le terme « remote » est essentiel. Contrairement à une Local Code Execution, qui suppose généralement que l’attaquant dispose déjà d’un accès local au système, une RCE peut être déclenchée au travers d’une interface accessible à distance : application web, API, service réseau, fonction d’upload, middleware, composant d’administration ou service exposé sur Internet ou sur un réseau interne.

Une RCE peut parfois être exploitable sans authentification. Dans d’autres situations, elle nécessite au préalable un compte utilisateur, l’accès à une fonctionnalité particulière ou la combinaison avec une autre vulnérabilité.

Sa criticité ne dépend donc pas uniquement de l’existence de la primitive d’exécution. Le contexte dans lequel cette exécution intervient joue un rôle déterminant.

RCE et injection de commandes : quelles différences ?

Les termes RCE et injection de commandes sont régulièrement utilisés de manière interchangeable alors qu’ils ne décrivent pas exactement la même chose.

  • Une injection de commandes est une vulnérabilité permettant à un attaquant d’influencer une commande destinée à être exécutée par le système d’exploitation. Si l’application transmet cette entrée à un shell et que l’attaquant parvient à injecter de nouvelles commandes, cette Injection peut aboutir à une RCE.
  • La RCE, quant à elle, décrit principalement le résultat obtenu : l’attaquant est capable d’exécuter à distance des instructions arbitraires dans le contexte du système cible.

Une chaîne d’exploitation peut par exemple être résumée ainsi : une donnée contrôlée par l’utilisateur est intégrée à une commande système, une injection devient possible, puis l’attaquant exécute whoami. La vulnérabilité initiale est une injection de commandes ; la capacité obtenue constitue une RCE.

Dans une autre application, aucune commande système n’est directement construite. L’attaquant exploite une désérialisation non sécurisée, trouve une chaîne de gadgets et atteint finalement une fonction capable de créer un processus. Le mécanisme est complètement différent, mais le résultat reste une RCE.

Cette distinction est importante lors d’un audit de sécurité : RCE indique l’impact tandis que la vulnérabilité sous-jacente explique généralement la cause et détermine la remédiation appropriée.

Pourquoi les RCE sont-elles particulièrement critiques ?

Une vulnérabilité applicative classique reste souvent confinée au contexte de l’application. Une RCE peut au contraire créer un point de jonction direct entre la surface applicative et l’environnement d’exécution sous-jacent.

Une application web qui s’exécute sous l’utilisateur www-data, un service Java fonctionnant avec un compte de service, une fonction serverless disposant d’une identité cloud ou un pod Kubernetes utilisant un service account possèdent tous des permissions et des accès particuliers.

Lorsqu’un attaquant obtient une RCE, il hérite généralement des capacités du processus compromis.

Il peut ainsi avoir accès à des fichiers de configuration, des secrets applicatifs, des identifiants de base de données, des API internes, des variables d’environnement, des tokens d’authentification, des services accessibles uniquement depuis le réseau interne ou des identités techniques permettant d’interagir avec une infrastructure cloud.

Une RCE n’équivaut toutefois pas automatiquement à une compromission totale.

Un service fortement isolé, exécuté sans privilège, dépourvu de secrets et soumis à des contrôles réseau stricts limitera considérablement l’impact. À l’inverse, une RCE obtenue dans un processus privilégié disposant d’identifiants sensibles et d’un accès réseau étendu peut constituer le début d’une compromission beaucoup plus importante.

Comment une vulnérabilité mène-t-elle à une RCE ?

Les mécanismes techniques permettant d’atteindre une RCE peuvent varier fortement, mais la plupart des scénarios comportent plusieurs éléments communs.

Une donnée contrôlée par un attaquant entre tout d’abord dans l’application. Cette donnée traverse ensuite différents traitements jusqu’à atteindre un composant capable de l’interpréter ou de déclencher des opérations sensibles.

La frontière entre donnée et instruction disparaît alors.

Entrées contrôlées par un attaquant

Les données utilisées dans une chaîne menant à une RCE ne proviennent pas nécessairement d’un champ de formulaire évident.

Elles peuvent être introduites au moyen d’un paramètre d’URL, d’un corps de requête JSON, d’un header HTTP, d’un cookie, d’un fichier, d’une archive, d’un message placé dans une file d’attente ou encore d’une donnée provenant d’une intégration tierce.

Une application peut également stocker une valeur dans sa base de données avant de l’utiliser plusieurs heures ou plusieurs jours plus tard dans un contexte dangereux. Une vulnérabilité peut donc être de second ordre : la donnée n’est pas dangereuse au moment où elle est enregistrée, mais le devient lorsqu’elle atteint ultérieurement un composant capable de l’interpréter.

Il est par conséquent insuffisant d’analyser uniquement le point d’entrée. Lors d’un pentest ou d’une revue de code, il faut suivre le chemin parcouru par les données jusqu’aux fonctions sensibles.

Les sinks et les contextes d’exécution

Un sink est une opération dans laquelle une donnée contrôlée devient particulièrement dangereuse.

Dans le contexte des RCE, cela peut notamment correspondre à l’exécution d’une commande système, l’évaluation de code, la compilation dynamique d’un template, la désérialisation d’un objet, le chargement dynamique d’une classe ou le traitement d’un fichier par un parser vulnérable.

Prenons une fonction de diagnostic réseau acceptant une adresse IP.

L’application pourrait construire :

os.system("ping -c 4 " + user_input)

La valeur fournie par l’utilisateur est initialement une simple chaîne de caractères. Mais dès qu’elle est concaténée à une commande transmise à un shell, certains caractères peuvent acquérir une signification particulière.

Une entrée telle que :

127.0.0.1 && whoami

peut alors conduire le shell à interpréter deux commandes distinctes.

Le problème n’est donc pas uniquement la présence d’une entrée utilisateur. Il réside dans le fait qu’une donnée non fiable atteint un interpréteur capable de lui donner une signification exécutable.

Contexte d’exécution et privilèges

Une fois l’exécution obtenue, le processus vulnérable détermine généralement les droits initiaux de l’attaquant.

Une RCE dans un serveur web exécuté sous un utilisateur fortement restreint n’aura pas immédiatement le même impact qu’une RCE dans un service système fonctionnant avec des privilèges élevés.

L’auditeur doit donc déterminer plusieurs éléments : quel utilisateur exécute le processus, quels fichiers lui sont accessibles, quelles ressources réseau il peut contacter, quelles variables d’environnement sont disponibles et quelles identités ou credentials sont associés au workload.

Dans les architectures modernes, ces derniers éléments sont souvent plus importants que la capacité à devenir administrateur du système d’exploitation.

Un conteneur non-root peut par exemple disposer d’un service account Kubernetes excessivement privilégié. Une fonction serverless peut posséder un rôle IAM lui donnant accès à plusieurs buckets, secrets ou bases de données. Une application web peut contenir dans son environnement un token permettant d’administrer un service tiers.

Ainsi, une RCE ayant des privilèges locaux apparemment faibles peut malgré tout présenter un impact majeur.

De la possibilité d’exécution à la compromission

La première commande exécutée lors d’un pentest vise généralement à démontrer l’existence de la vulnérabilité et non à compromettre réellement le système.

Une commande simple telle que whoami ou id permet de confirmer l’exécution et d’identifier le contexte initial.

L’étape suivante consiste à qualifier l’impact en restant dans les limites définies pour l’audit : système d’exploitation, droits du processus, accès aux configurations, présence de secrets, connectivité interne et éventuelles identités techniques accessibles.

La différence entre une primitive d’exécution et une compromission importante dépend alors des défenses entourant l’application : principe du moindre privilège, segmentation réseau, protection des secrets, isolation du runtime, restrictions des identités cloud et supervision.

Comment identifier et tester une RCE lors d’un pentest ?

La recherche de RCE ne consiste pas à envoyer indistinctement des payloads vers tous les paramètres d’une application.

Une approche efficace commence par la compréhension des fonctionnalités et des technologies susceptibles d’introduire un contexte d’exécution.

Cartographier les fonctionnalités sensibles

Certaines fonctionnalités méritent une attention particulière : outils de diagnostic, conversion de documents, génération de rapports, traitements d’images, compilation ou rendu de templates, mécanismes d’import/export, uploads, systèmes de plugins, fonctions d’administration ou intégrations exécutant des commandes externes.

L’auditeur cherche également à identifier les technologies utilisées. Connaître le langage, le framework, le moteur de template, le système d’exploitation ou les bibliothèques de parsing permet d’orienter les tests.

Lors d’un audit white box, cette phase peut être accélérée par une recherche des fonctions dangereuses dans le code source : appels à des shells, eval, désérialisation native, création dynamique de templates ou lancement de processus.

Valider l’exécution avec un impact minimal

L’objectif initial est de démontrer la vulnérabilité avec une preuve aussi peu intrusive que possible.

Dans une injection de commandes, l’auditeur peut utiliser une commande qui produit un résultat facilement identifiable.

Dans une SSTI, une opération telle que {{7*7}} peut suffire à démontrer que l’expression est évaluée côté serveur.

Dans un contexte de désérialisation, une modification contrôlée ou un callback inoffensif peut permettre de confirmer le comportement avant de chercher une chaîne plus complexe.

Cette approche progressive limite les risques de perturbation et évite de déclencher inutilement des actions destructrices.

RCE avec retour direct

Le cas le plus simple correspond à une exécution dont la sortie est renvoyée dans la réponse de l’application.

L’auditeur envoie par exemple une instruction permettant d’afficher l’utilisateur courant et observe la valeur retournée. Ce type de RCE facilite énormément les investigations puisque chaque commande peut produire un résultat directement visible.

Toutes les vulnérabilités ne fonctionnent cependant pas ainsi.

Blind RCE et validation temporelle

Dans une blind RCE, la commande est exécutée mais sa sortie n’est jamais affichée. L’auditeur peut alors chercher un effet indirect mesurable.

Un exemple classique consiste à provoquer volontairement un délai : sleep 5

Si la réponse prend systématiquement environ cinq secondes supplémentaires lorsque le payload est envoyé, il existe un signal intéressant.

Une seule mesure ne suffit toutefois pas. Les latences réseau, mécanismes de cache ou traitements asynchrones peuvent produire des faux positifs. Il est préférable de reproduire plusieurs fois le comportement et de comparer différentes durées.

Validation Out-of-Band

Certaines RCE ne produisent ni sortie visible ni délai exploitable. Une validation Out-of-Band (OOB) peut alors être utilisée dans le cadre contrôlé d’un pentest.

Le principe consiste à déclencher depuis le serveur un accès DNS ou HTTP vers une infrastructure contrôlée par l’auditeur. L’observation de cette interaction confirme que la commande ou le code a effectivement été exécuté.

Cette technique est particulièrement utile pour les désérialisations aveugles, les injections exécutées de manière asynchrone ou certains traitements de fichiers.

Exemples d’exploitations courantes de failles RCE

Injection de commandes : de l’entrée utilisateur à la RCE

L’injection de commandes constitue l’un des chemins les plus directs vers une RCE.

Elle apparaît lorsqu’une application construit une commande système en utilisant une donnée contrôlée par un utilisateur sans garantir une séparation suffisante entre les arguments et la syntaxe interprétée par le shell.

Comment fonctionne une injection de commandes ?

Considérons une fonctionnalité permettant de tester la connectivité vers une machine.

Le serveur exécute :

<?php
$host = $_GET['host'];
system("ping -c 4 " . $host);
?>

Un appel légitime contenant :

127.0.0.1

produit :

ping -c 4 127.0.0.1

Mais une valeur comme :

127.0.0.1 && whoami

peut conduire le shell à exécuter successivement le ping puis whoami.

L’application ne distingue plus la partie qui devait représenter une adresse IP de celle qui introduit de nouvelles instructions.

Opérateurs shell et différences entre environnements

Les shells prennent en charge de nombreux opérateurs permettant de chaîner ou modifier des commandes : ;, &&, ||, pipes, substitutions de commandes ou retours à la ligne.

Le comportement précis dépend cependant du shell et du système cible.

Les syntaxes valables sous Bash ne sont pas nécessairement identiques à celles de cmd.exe ou PowerShell. Cette différence est importante lors des tests : une payload inefficace ne signifie pas nécessairement que le paramètre est sécurisé.

Exemples de code vulnérable

  • En Python : os.system("ping -c 4 " + user_input)
  • En Node.js : exec("ping -c 4 " + host)
  • En PHP : system("ping -c 4 " . $_GET['host']);

Le point commun n’est pas le langage, mais la construction dynamique d’une instruction dont une partie est contrôlée par l’utilisateur.

Une implémentation Python préférable pourrait utiliser :

subprocess.run(
    ["ping", "-c", "4", user_input],
    shell=False,
    check=False
)

Les arguments sont ici séparés et ne sont pas interprétés comme une nouvelle syntaxe shell.

Cela ne dispense pas de valider la valeur de user_input, mais réduit fortement le risque de transformer une donnée en instruction supplémentaire.

Injection de commandes aveugle

Il arrive que la sortie des commandes ne soit pas présente dans la réponse HTTP.

Une charge provoquant un délai peut alors permettre de déterminer si l’entrée atteint réellement le shell.

De même, une interaction OOB contrôlée peut confirmer que le système est capable d’initier une communication externe à la suite du traitement de la payload.

Les tests doivent être adaptés au contexte : certaines commandes peuvent être filtrées, indisponibles dans l’image système ou exécutées dans un environnement très restreint.

Pourquoi les blacklists fonctionnent mal

Une défense consistant à supprimer uniquement ; ou && est rarement suffisante.

Les shells possèdent de nombreuses syntaxes, plusieurs couches de décodage peuvent intervenir et les règles peuvent différer selon le système d’exploitation.

Des transformations d’encodage, des retours à la ligne ou des constructions spécifiques au shell peuvent contourner une protection qui cherche seulement quelques caractères.

La bonne stratégie consiste donc à supprimer le besoin d’interprétation shell, et non à essayer d’énumérer toutes les syntaxes potentiellement dangereuses.

Comment prévenir les injections de commandes ?

Lorsque cela est possible, l’application doit utiliser des APIs natives plutôt que lancer des utilitaires système.

Si l’exécution d’un programme externe est réellement nécessaire, les arguments doivent être fournis séparément sans passer par un shell. Une allowlist stricte doit limiter les valeurs autorisées lorsque le domaine fonctionnel le permet.

L’application doit en complément fonctionner avec des privilèges minimaux. Ainsi, même si une erreur de développement subsiste, l’impact d’une éventuelle exécution est réduit.

Désérialisation non sécurisée et RCE

La désérialisation non sécurisée représente un chemin beaucoup moins intuitif vers l’exécution de code.

L’attaquant n’injecte pas nécessairement une commande directement. Il tente plutôt de manipuler le processus de reconstruction d’objets afin de provoquer des comportements inattendus au sein de l’application.

Qu’est-ce que la désérialisation ?

La sérialisation transforme un objet en une représentation pouvant être stockée ou transmise. La désérialisation effectue l’opération inverse et reconstruit l’objet dans le runtime.

Les applications utilisent ces mécanismes pour gérer des sessions, caches, files de messages, communications entre services ou données persistées.

Le problème apparaît lorsqu’une application accepte une structure sérialisée contrôlée par un utilisateur et la reconstruit avec un mécanisme susceptible d’instancier des classes ou d’exécuter automatiquement certains comportements.

Pourquoi désérialiser un objet peut exécuter du code ?

Certains langages disposent de méthodes ou callbacks appelés automatiquement pendant la reconstruction, l’accès ou la destruction d’objets.

En PHP, certaines méthodes magiques peuvent être concernées. Java utilise notamment readObject(). Python pickle peut reconstruire des objets en invoquant des fonctions spécifiées dans leur représentation.

Une donnée qui semble n’être qu’une description d’objet peut donc provoquer une suite d’appels applicatifs.

Une désérialisation dangereuse n’est pas automatiquement une RCE

Cette nuance est fondamentale. La présence de unserialize($userControlledData); constitue un comportement dangereux, mais ne signifie pas nécessairement qu’une chaîne RCE exploitable existe immédiatement.

L’attaquant doit généralement identifier les classes disponibles dans l’application ou ses dépendances et déterminer si certaines d’entre elles peuvent être combinées pour atteindre une opération sensible.

Les gadget chains

Un gadget est une classe ou une méthode existante présentant un comportement utile pour l’attaquant lorsqu’elle est appelée dans certaines conditions.

Plusieurs gadgets peuvent être enchaînés pour construire une gadget chain.

Une première classe peut déclencher automatiquement une méthode. Celle-ci peut manipuler un autre objet, qui finit à son tour par invoquer une fonction capable d’écrire un fichier, charger du code ou créer un processus.

L’attaquant ne fournit donc pas forcément son propre code. Il détourne les composants déjà présents dans l’application.

C’est l’une des raisons pour lesquelles les grandes applications Java, PHP ou .NET peuvent présenter une surface particulièrement importante : leurs dépendances mettent parfois à disposition un grand nombre de classes susceptibles d’être utilisées comme gadgets.

Exemple avec Python pickle

Un comportement tel que :

import pickle

data = request.cookies.get("session")
obj = pickle.loads(data)

est particulièrement risqué si le cookie peut être contrôlé par un attaquant.

pickle n’est pas conçu comme un format de données sûr face à des entrées non fiables. La reconstruction peut déclencher l’invocation de fonctions Python.

Pour transporter des données fournies par un utilisateur, il est généralement préférable d’utiliser un format purement déclaratif et validé selon un schéma adapté.

Signature et intégrité des objets

Signer cryptographiquement une donnée sérialisée peut empêcher un attaquant de la modifier lorsqu’il ne dispose pas de la clé.

Cette mesure peut être utile, mais elle ne rend pas intrinsèquement sûr un mécanisme de désérialisation dangereux.

Si une autre source non fiable peut fournir une structure sérialisée, si la clé est compromise ou si l’application signe elle-même des objets malveillants à travers une autre fonctionnalité, le risque réapparaît.

La meilleure protection reste donc d’éviter de désérialiser des objets natifs provenant de sources non fiables lorsque cela n’est pas indispensable.

Server-Side Template Injection (SSTI) et RCE

Les moteurs de templates serveur sont utilisés pour générer dynamiquement des pages HTML, des emails, des documents ou différents contenus.

Ils sont normalement conçus pour recevoir un template défini par le développeur et des données qui seront injectées dans ce template.

Une SSTI apparaît lorsque des données contrôlées par un attaquant deviennent elles-mêmes une partie du template interprété.

Données dans un template ou template contrôlé : une différence essentielle

Une construction normale pourrait être :

template = Template("Bonjour {{ name }}")
return template.render(name=user_input)

La variable name est traitée comme une donnée. Une construction dangereuse serait :

template = Template("Bonjour " + user_input)
return template.render()

Si user_input contient {{7*7}} et que la réponse contient 49, cela signifie que la donnée utilisateur a été intégrée au langage du moteur de template.

De l’évaluation à l’exploitation de la RCE

La possibilité d’effectuer une multiplication ne constitue évidemment pas encore une RCE. L’auditeur doit déterminer quelles primitives sont accessibles dans le moteur concerné.

Certains moteurs permettent d’accéder à des objets internes, des fonctions, des classes ou au contexte de l’application. Dans certains environnements, ces capacités peuvent être utilisées pour atteindre des fonctions permettant l’accès à des fichiers ou l’exécution de processus.

Cette progression dépend fortement du moteur de template, de sa version, de sa configuration et des objets exposés au rendu.

Il n’existe donc pas de payload SSTI universelle.

Sandboxing et limites

Certains moteurs disposent de mécanismes de sandbox visant à restreindre les attributs ou fonctions accessibles depuis les templates.

Cette protection est utile mais ne doit pas justifier l’évaluation arbitraire de templates fournis par des utilisateurs. L’histoire de plusieurs moteurs montre que des primitives inattendues, des objets indirectement exposés ou des contournements de sandbox peuvent apparaître.

La stratégie la plus robuste reste d’éviter qu’un utilisateur puisse définir lui-même une expression exécutée par le moteur.

Upload de fichiers et RCE

Les fonctionnalités d’upload sont courantes : images de profil, documents, PDF, archives, vidéos ou exports métiers. Le simple fait qu’un utilisateur puisse envoyer un fichier n’est pas une vulnérabilité.

Le risque dépend de ce que le système fait ensuite de ce fichier.

Exécution directe d’un fichier uploadé

Le cas le plus classique concerne une application qui stocke des fichiers utilisateurs dans un répertoire accessible par le serveur web et dans lequel l’exécution de scripts est autorisée.

Un handler vulnérable pourrait simplement copier le fichier fourni dans :

/uploads/

Si le serveur web interprète ensuite un fichier PHP placé dans ce dossier, un attaquant capable d’envoyer un script peut obtenir une capacité d’exécution.

La remédiation ne se limite donc pas au contrôle des extensions.

Les contenus devraient être stockés en dehors des répertoires exécutables et, si possible, servis par un composant incapable d’interpréter des scripts.

Doubles extensions et MIME types : attention aux simplifications

Une extension comme shell.php.jpg n’est pas automatiquement exécutée comme du PHP. Son comportement dépend de la configuration du serveur web, des handlers associés aux extensions, d’éventuelles règles de réécriture et du système de traitement utilisé.

Une double extension constitue donc une technique de contournement possible dans certains environnements, mais pas une propriété intrinsèque des serveurs web.

Même principe pour le header :

Content-Type: image/jpeg

Ce champ étant contrôlé par le client, il ne constitue pas à lui seul une preuve que le fichier est réellement une image.

Le serveur doit vérifier le format attendu et ne jamais se reposer uniquement sur les informations déclarées par l’utilisateur.

La RCE peut se produire sans que le fichier soit exécutable

Les applications modernes traitent fréquemment les fichiers après leur envoi. Une image peut être redimensionnée, un PDF converti, une archive extraite, etc.

Le fichier peut alors atteindre des bibliothèques telles que des moteurs d’images, parsers PDF, outils multimédias ou autres composants natifs.

Si l’un de ces composants contient une vulnérabilité exploitable, un fichier spécialement construit peut provoquer une exécution lors du traitement.

Dans cette situation, le fichier n’est jamais directement exécuté par le serveur web. La RCE se produit dans la chaîne de parsing ou de transformation.

Inclusion de fichiers et chaînes vers une RCE

Certaines vulnérabilités d’inclusion de fichiers peuvent également participer à une chaîne menant à l’exécution.

Une LFI permet initialement de lire ou inclure un fichier local. Selon le langage et la configuration, cette primitive peut parfois être combinée avec une capacité d’écriture, un fichier temporaire, une session ou une autre source de contenu contrôlée afin de provoquer l’interprétation de code.

Une RFI peut dans certains environnements permettre l’inclusion directe d’une ressource distante.

Ces scénarios dépendent fortement de la plateforme et doivent être distingués d’une simple lecture arbitraire de fichiers.

D’une injection SQL à une RCE

Une injection SQL donne initialement à un attaquant la possibilité d’influencer les requêtes envoyées à une base de données.

Elle n’implique pas nécessairement une exécution de code sur le système.

Néanmoins, certains SGBD disposent de fonctionnalités permettant d’interagir avec le système d’exploitation, d’écrire des fichiers, d’exécuter des extensions ou de charger des composants.

Lorsque le compte utilisé par l’application possède des privilèges excessifs, une SQL Injection peut donc parfois être transformée en primitive d’exécution.

Cela illustre une fois encore l’importance du principe du moindre privilège.

Une application qui n’a besoin que de lire et modifier quelques tables ne devrait pas se connecter à sa base avec un compte disposant de capacités administratives ou système.

Autres chemins susceptibles de mener à une RCE

Il serait impossible de dresser une liste exhaustive des vulnérabilités pouvant aboutir à une RCE.

Des problèmes de corruption mémoire dans des services écrits en C ou C++, des systèmes de plugins mal isolés, des pipelines CI/CD permettant l’exécution de scripts, des mécanismes de compilation dynamique ou certaines chaînes combinant écriture de fichiers et chargement de modules peuvent également produire une capacité d’exécution.

Le point commun n’est donc pas une technologie précise.

Il s’agit toujours d’identifier le moment où une donnée ou une action contrôlée par l’attaquant franchit une frontière et atteint un mécanisme suffisamment puissant pour exécuter des instructions non prévues.

Que peuvent êtres les impacts d’une RCE ?

L’obtention d’une RCE n’est souvent que le début d’une chaîne d’attaque. Les possibilités dépendent intégralement de l’environnement compromis.

Identifier le contexte d’exécution

Les premières informations pertinentes concernent généralement le runtime lui-même : utilisateur, système d’exploitation, répertoire courant, variables d’environnement, processus, fichiers accessibles et interfaces réseau.

L’objectif est de comprendre quelles frontières de sécurité entourent le processus. Une RCE obtenue dans une application isolée n’offre pas nécessairement une visibilité sur le reste de l’infrastructure.

Secrets applicatifs

Les applications ont fréquemment besoin de secrets pour fonctionner. Ils peuvent être présents dans des fichiers de configuration, variables d’environnement, mécanismes de secret management, fichiers de credentials ou volumes montés.

On retrouve notamment des identifiants de bases de données, tokens API, clés de signature ou secrets utilisés pour communiquer avec d’autres services.

Une RCE peut donc permettre de récupérer des informations qui donnent accès à des ressources plus sensibles que le serveur initial.

Accès au réseau interne

Une application exposée sur Internet est souvent connectée à des services non accessibles directement depuis l’extérieur : bases de données, APIs internes, services d’administration, caches, queues ou systèmes métier.

La compromission du serveur modifie donc la position réseau de l’attaquant.

Une segmentation adaptée doit empêcher qu’un workload puisse communiquer avec n’importe quel système interne sans justification fonctionnelle.

RCE dans un conteneur

Une RCE dans une application conteneurisée correspond initialement à une compromission du contexte du conteneur, et non nécessairement du nœud hôte.

L’impact dépend notamment de l’utilisateur utilisé, des Linux capabilities, des volumes montés, de l’accès éventuel à des sockets sensibles et des restrictions appliquées par le runtime.

Un conteneur non-root avec un filesystem fortement restreint et aucune capacité supplémentaire réduit considérablement la surface de post-exploitation.

À l’inverse, un conteneur privilégié ou doté de montages sensibles peut transformer une compromission applicative en problème beaucoup plus large.

RCE dans Kubernetes

Dans Kubernetes, il est également nécessaire d’analyser l’identité attribuée au pod.

Un service account peut disposer de droits lui permettant de lire certains Secrets, interroger l’API Kubernetes ou manipuler des ressources.

Une application peut donc être exécutée sans privilège au niveau Linux tout en disposant d’une identité Kubernetes trop puissante.

La sécurité repose alors autant sur le RBAC que sur les contrôles du système d’exploitation.

Il est recommandé de limiter les permissions des service accounts, de désactiver les montages de tokens lorsqu’ils ne sont pas nécessaires et de segmenter les communications entre workloads.

RCE et environnements cloud

Dans une infrastructure cloud, les workloads possèdent fréquemment une identité permettant d’interagir avec les APIs du fournisseur.

Une RCE peut donc permettre à un attaquant d’utiliser indirectement ces permissions.

Le risque dépend de la configuration de l’identité concernée : accès à du stockage objet, lecture de secrets, administration de services, fonctions de déploiement ou autres permissions IAM.

Les services de métadonnées constituent également un point d’attention, même si leur fonctionnement et leurs protections diffèrent selon les fournisseurs et les configurations.

L’objectif ne doit donc pas être uniquement d’empêcher une RCE, mais de s’assurer que la compromission d’un workload unique ne donne pas automatiquement accès à l’ensemble de l’environnement cloud.

Comment prévenir les RCE ?

La prévention efficace des RCE repose sur une stratégie de défense en profondeur. Il faut simultanément réduire les possibilités d’obtenir une primitive d’exécution, limiter l’impact d’une éventuelle compromission et améliorer les capacités de détection.

Empêcher les données de devenir des instructions

Le premier objectif consiste à conserver une séparation stricte entre les données fournies par les utilisateurs et les mécanismes d’exécution.

  • Les commandes système doivent être évitées lorsqu’une API native peut remplir la même fonction.
  • Lorsqu’un processus externe est indispensable, les arguments doivent être transmis séparément sans interprétation par un shell.
  • Les moteurs de templates doivent recevoir des variables plutôt que des templates construits depuis des entrées utilisateurs.
  • Les fonctions eval ou équivalentes ne doivent pas être appliquées à des données non fiables.
  • Les formats de désérialisation capables de reconstruire des objets exécutables doivent être remplacés par des représentations structurées et strictement validées lorsqu’ils traitent des données externes.

Sécuriser les uploads et les parsers

Les fichiers envoyés par les utilisateurs doivent être considérés comme non fiables. Le contrôle doit porter sur le format attendu, la taille, le contenu et le comportement du pipeline de traitement.

Les fichiers ne devraient pas être stockés dans des répertoires permettant l’exécution de scripts. Les opérations de conversion, extraction ou parsing doivent être isolées autant que possible et les bibliothèques utilisées doivent rester à jour.

Gérer les dépendances

Les applications modernes dépendent fortement de bibliothèques tierces, de frameworks, de plugins et de services externes. De nombreuses vulnérabilités RCE proviennent donc non pas du code applicatif propre, mais de dépendances vulnérables intégrées à la stack logicielle. Des incidents majeurs comme Log4Shell ont démontré comment une seule bibliothèque vulnérable peut exposer rapidement des milliers de systèmes au RCE.

Les attaquants ciblent fréquemment des frameworks obsolètes, des bibliothèques de désérialisation vulnérables, des moteurs de template exposés, des outils de traitement média non sécurisés, ou des composants serveur non patchés. Une gestion efficace des dépendances joue donc un rôle critique : inventorier en continu les dépendances, surveiller les nouvelles vulnérabilités divulguées, automatiser le scan de dépendances, supprimer les paquets inutiles, et appliquer rapidement les correctifs de sécurité.

Appliquer le principe du moindre privilège

Un processus vulnérable ne devrait avoir accès qu’aux ressources nécessaires à sa fonction.

  • Les applications ne doivent pas s’exécuter en root sans justification.
  • Les comptes de service doivent posséder des permissions limitées.
  • Les accès aux bases, APIs internes et ressources cloud doivent être restreints.

Cette approche ne supprime pas la vulnérabilité, mais limite la capacité de l’attaquant à transformer une première exécution en compromission globale.

Isoler les workloads

Les mécanismes d’isolation – conteneurs, sandbox, namespaces, profils système, politiques réseau – permettent de réduire les interactions disponibles après exploitation.

Un workload de traitement de fichiers peut par exemple être isolé d’Internet et des ressources sensibles.

Un service de rendu de documents n’a pas nécessairement besoin de communiquer avec la base de données de production.

La segmentation doit refléter les besoins réels des composants plutôt qu’une confiance implicite envers l’ensemble du réseau interne.

Sécuriser Kubernetes et le cloud

Les conteneurs doivent être exécutés avec des utilisateurs non privilégiés lorsque cela est possible et avec un nombre minimal de capabilities.

Les workloads Kubernetes doivent disposer de service accounts spécifiquement adaptés à leurs besoins et d’un RBAC restrictif.

Les communications inter-services doivent être limitées avec des politiques réseau appropriées.

Dans le cloud, les identités de workloads doivent suivre le principe du moindre privilège et les accès aux secrets ou services sensibles doivent être fortement contrôlés.

Un attaquant ayant compromis une seule application ne devrait pas pouvoir utiliser son identité pour administrer une partie importante de l’infrastructure.

Mettre en place une surveillance comportementale

La prévention parfaite n’existe pas.

Une défense réaliste suppose donc qu’une vulnérabilité inconnue ou non corrigée puisse un jour être exploitée.

La supervision doit permettre d’identifier les comportements incompatibles avec le fonctionnement normal des applications : création de shells, nouveaux processus, accès à des outils système, communications sortantes inhabituelles, erreurs de parsing répétées ou interactions anormales avec les APIs cloud.

La combinaison de logs applicatifs, EDR, télémétrie réseau et logs de plateformes cloud augmente fortement les possibilités de détection.

Conclusion

Une Remote Code Execution ne correspond pas à un mécanisme technique unique. Elle représente généralement l’aboutissement d’une chaîne dans laquelle une donnée ou une action contrôlée par un attaquant atteint un contexte capable de l’interpréter comme une instruction ou de déclencher une opération dangereuse.

Une injection de commandes peut permettre de contrôler directement un shell. Une SSTI peut progressivement exposer des primitives internes conduisant à la création d’un processus. Une désérialisation peut détourner les classes présentes dans une application. Un fichier peut être interprété directement ou exploiter un parser vulnérable. Enfin, un composant tiers peut introduire une RCE indépendamment du code développé par l’organisation.

La gravité réelle ne s’arrête cependant pas à l’obtention de l’exécution.

Elle dépend de ce que le processus compromis est autorisé à faire : lire des secrets, accéder au réseau interne, utiliser une identité cloud, manipuler des ressources Kubernetes ou interagir avec d’autres systèmes.

La prévention des RCE doit donc être envisagée à plusieurs niveaux. Le développement sécurisé doit empêcher les données non fiables d’atteindre les primitives d’exécution. La gestion des dépendances doit réduire l’exposition aux vulnérabilités connues. Le principe du moindre privilège et l’isolation doivent limiter l’impact d’une éventuelle compromission. Enfin, la surveillance comportementale doit permettre d’identifier les tentatives d’exploitation qui parviendraient malgré tout à franchir ces premières lignes de défense.

C’est cette combinaison entre prévention, réduction de l’impact et détection qui permet de traiter efficacement le risque associé aux RCE.