# Empêcher l'injection de commandes en PHP

> Empêcher l'injection de commandes en PHP : éviter le shell, proc_open avec tableau, bien utiliser escapeshellarg et repérer les appels dangereux dans le code.

Source : https://phpsos.de/fr/blog/command-injection-php/  
État : 25/10/2026  
Fournisseur : PHP SOS, Assistance d'urgence PHP et sécurité des serveurs. Aide immédiate par WhatsApp : https://wa.me/491736494914?text=Bonjour%20PHP%20SOS%2C%20nous%20avons%20besoin%20d%27aide%20avec%20notre%20serveur%20PHP.%20Entreprise%20%3A%20%E2%80%A6%20Site%20web%20%3A%20%E2%80%A6

**En bref :** le moyen le plus sûr d'empêcher l'injection de commandes en PHP est de ne pas appeler de shell du tout : de nombreuses tâches sont accomplies directement par des fonctions PHP ou des bibliothèques. Lorsqu'un programme externe est nécessaire, transmettez la commande sous forme de tableau à `proc_open()` depuis PHP 7.4 ; PHP lance alors le programme sans shell. Vous contrôlez les valeurs par rapport à une liste autorisée, clôturez les options avec `--` et faites tourner le processus avec des droits minimaux.

## Pourquoi un appel système donne tant de pouvoir

Retouche d'images, génération de PDF, archivage, analyse antivirus, interrogation d'une entrée DNS : pour ces tâches, les applications PHP appellent volontiers des programmes externes. Sur le plan technique, c'est pratique, une ligne avec `exec()` ou `shell_exec()` suffit. Sur le plan de la sécurité, c'est l'un des endroits les plus dangereux du code.

Lorsqu'une commande est assemblée sous forme de texte et exécutée par un shell, c'est le shell qui décide de ce qui est commande et de ce qui est donnée. Si une valeur extérieure y pénètre sans contrôle, elle peut modifier le sens de la commande. Dans le pire des cas, la conséquence est la même qu'avec PHP2Shell : exécution de commandes arbitraires sur le serveur avec les droits du processus PHP.

La différence : PHP2Shell se corrige par une mise à jour de PHP. Une injection de commandes dans votre propre code subsiste après chaque mise à jour. S'y ajoute le fait que de tels points se cachent souvent dans des scripts utilitaires prévus pour les seuls administrateurs, par exemple pour la maintenance, le diagnostic ou les imports, et qui restent ensuite accessibles sans protection.

## Où naît l'injection de commandes en PHP

Tout endroit où PHP lance un shell ou un programme est exposé. Les fonctions concernées sont :

| Fonction | Utilise un shell ? | Remarque |
|---|---|---|
| `exec()`, `system()`, `passthru()`, `shell_exec()` | oui | Commande sous forme de texte, interprétée par le shell |
| Opérateur backtick | oui | équivaut à `shell_exec()` |
| `popen()` | oui | Commande sous forme de texte |
| `proc_open()` avec chaîne | oui | Commande sous forme de texte |
| `proc_open()` avec tableau | non | depuis PHP 7.4, les arguments sont transmis directement |
| `mail()` avec cinquième paramètre | indirectement | paramètres supplémentaires pour le programme de messagerie |

Un schéma typiquement dangereux se présente ainsi. Le nom de fichier provient de la requête et atterrit directement dans la ligne de commande :

```php
<?php
// DANGEREUX : une valeur de la requête devient partie d'une commande shell
$datei = $_POST['datei'];
exec('convert uploads/' . $datei . ' -resize 800x800 vorschau/' . $datei);
```

## Premier choix : pas de shell du tout

La protection la plus efficace consiste à remplacer l'appel système. L'OWASP le cite expressément comme première ligne de défense. Pour de nombreuses tâches, PHP dispose de ses propres fonctions ou extensions :

- Répertoires et fichiers : `mkdir()`, `copy()`, `rename()`, `unlink()`
- Archives : `ZipArchive` ou `PharData`
- Images : GD ou Imagick comme extension PHP
- Requêtes réseau : `dns_get_record()`, `gethostbyname()`, cURL
- Sommes de contrôle : `hash_file()`

La question de savoir quels caractères un shell interprète disparaît alors complètement. De plus, les fonctions de commandes système peuvent alors être bloquées dans la configuration, comme le décrit le guide [Bien utiliser disable_functions](https://phpsos.de/fr/ratgeber/disable-functions/).

## Quand un programme externe est nécessaire : proc_open avec tableau

Depuis PHP 7.4, `proc_open()` accepte la commande sous forme de tableau. Selon le manuel PHP, le processus est alors lancé directement, sans passer par le shell, et PHP se charge du masquage nécessaire des arguments. Des caractères comme le point-virgule ou le pipe n'ont alors plus de signification particulière.

```php
<?php
declare(strict_types=1);

$name = basename((string) ($_POST['datei'] ?? ''));
if (!preg_match('/^[a-z0-9_-]{1,64}\.(jpg|png)$/', $name)) {
    throw new InvalidArgumentException('Ungültiger Dateiname.');
}

$befehl = ['/usr/bin/convert', '/srv/app/uploads/' . $name, '-resize', '800x800',
           '/srv/app/vorschau/' . $name];

$prozess = proc_open($befehl, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
if (!is_resource($prozess)) {
    throw new RuntimeException('Programm konnte nicht gestartet werden.');
}
$fehlerausgabe = stream_get_contents($pipes[2]);
$status = proc_close($prozess);
```

Trois éléments rendent l'exemple robuste : le programme figure dans le code avec son chemin complet, le nom de fichier est contrôlé par rapport à un motif étroit, et chaque argument variable commence par un chemin absolu, de sorte qu'il ne peut pas être lu comme une option. Lorsqu'une valeur se trouve seule, clôturez les options avec `--`, si le programme le permet selon sa documentation, ou rejetez les valeurs qui commencent par un tiret.

Pour les tâches qui exigent régulièrement des programmes externes, par exemple un traitement d'images ou de PDF à plus grande échelle, un découplage vaut la peine : l'application web se contente de déposer une tâche dans une file d'attente, et un service d'arrière-plan distinct, avec son propre utilisateur et ses propres droits, l'exécute. Le processus PHP qui répond aux requêtes venant d'Internet n'a alors plus besoin des fonctions de commandes système, et elles peuvent lui être bloquées. Les programmes externes devraient en outre avoir des limites de temps et de mémoire, afin qu'une tâche isolée ne bloque pas le serveur.

`mail()` est un cas particulier. Le cinquième paramètre transmet des options supplémentaires au programme de messagerie du système. Des valeurs issues de formulaires, par exemple une adresse d'expéditeur, ne doivent jamais y aboutir sans contrôle. Une bibliothèque de messagerie qui envoie par SMTP et n'a pas besoin d'appel système est plus robuste.

## escapeshellarg : bien l'utiliser et connaître ses limites

Là où le code existant travaille encore avec des lignes de commande, `escapeshellarg()` pour chaque valeur individuelle est la mesure minimale. La fonction place la valeur entre guillemets, de sorte que le shell la traite comme exactement un argument. `escapeshellcmd()` est inadapté aux saisies d'utilisateur, car il admet des paramètres supplémentaires, comme l'OWASP le montre par un exemple.

Limites importantes de `escapeshellarg()` :

1. **L'injection d'arguments reste possible :** une valeur masquée qui commence par un tiret est tout de même lue comme une option par le programme cible.
2. **Windows se comporte différemment :** selon le manuel, la fonction y remplace certains caractères comme `%` et `"` par des espaces ; les valeurs arrivent donc modifiées.
3. **Jeu de caractères :** selon le réglage de locale du serveur, des caractères non ASCII peuvent être perdus.

## Repérer les occurrences dans le code existant

Une recherche des appels système est vite faite et donne une liste maîtrisable :

```bash
grep -rnE "\b(exec|system|passthru|shell_exec|popen|proc_open)\s*\(" src/ --include="*.php"
grep -rn '`' src/ --include="*.php" | grep -v '^\s*//'
```

Chaque occurrence appelle trois réponses : l'appel peut-il être remplacé par des fonctions PHP ? Des valeurs extérieures y circulent-elles ? Avec quels droits le processus s'exécute-t-il ? La dernière question est décisive pour les dégâts en cas d'incident. Un processus PHP avec son propre utilisateur et sans droits d'écriture sur le code fait moins de dégâts, voir [Moindre privilège : les droits de l'utilisateur du serveur web](https://phpsos.de/fr/blog/rechte-des-webserver-benutzers/).

> **Important:**
> Si un processus PHP-FPM ou du serveur web lance un shell, c'est un signal d'alerte fort d'une intrusion. La façon de le reconnaître est décrite dans [Signal d'alerte : le serveur web lance un shell](https://phpsos.de/fr/blog/webserver-startet-shell/).

## Comment PHP SOS aide-t-il concrètement ?

- Nous recherchons tous les appels système dans votre application, vos extensions et vos scripts utilitaires.
- Nous remplaçons les appels, dans la mesure du possible, par des fonctions PHP et faisons passer les autres à des appels sans shell.
- Nous bloquons les fonctions inutiles dans la configuration de PHP et vérifions les droits de l'utilisateur PHP.
- Nous testons les modifications et documentons quels programmes l'application peut encore appeler.

Nous proposons la vérification de logiciels internes sous forme d'[audit de sécurité et test d'intrusion](https://phpsos.de/fr/leistungen/sicherheitsaudit-penetrationstest/).


## Questions fréquentes

**escapeshellarg suffit-il contre l'injection de commandes ?**  
Il empêche qu'une valeur soit exécutée comme une commande supplémentaire. Il n'empêche pas que la valeur soit lue comme une option du programme appelé si elle commence par un tiret. Il faut donc en plus contrôler les valeurs et, lorsque le programme le permet, clore les options avec --.

**Quelle est la différence entre escapeshellarg et escapeshellcmd ?**  
escapeshellarg traite une valeur isolée comme exactement un argument. escapeshellcmd ne masque que les métacaractères dans une ligne de commande entière et admet des paramètres supplémentaires. Pour les saisies d'utilisateur, escapeshellarg est le bon choix, et un appel sans shell est encore préférable.

**Dois-je bloquer exec et system via disable_functions ?**  
Si votre application n'en a pas besoin, oui. Cela limite les dégâts après une intrusion. Cela ne remplace toutefois pas un code sûr, car les applications qui ont besoin de commandes continuent d'avoir besoin de ces fonctions.

**Comment savoir si une injection de commandes a été exploitée ?**  
Un signal d'alerte fort est constitué par des processus PHP ou du serveur web qui lancent un shell comme sh ou bash. S'y ajoutent des processus inconnus, de nouveaux fichiers dans les dossiers d'upload et des connexions sortantes inhabituelles.

## Sources

- [Aide-mémoire OWASP sur la défense contre l'injection de commandes système (OS Command Injection Defense Cheat Sheet)](https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html)
- [Manuel PHP : proc_open](https://www.php.net/manual/de/function.proc-open.php)
- [Manuel PHP : escapeshellarg](https://www.php.net/manual/de/function.escapeshellarg.php)
