# Comment PHP traite les uploads de fichiers

> Comment PHP traite les uploads en interne : multipart/form-data, fichiers temporaires dans upload_tmp_dir, tableau $_FILES, limites et codes d'erreur expliqués.

Source : https://phpsos.de/fr/php/datei-uploads-intern/  
É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 :** lorsqu'un navigateur téléverse un fichier, il envoie une requête POST au format `multipart/form-data`, dans laquelle les champs du formulaire et le contenu des fichiers se suivent sous forme de parties distinctes. PHP décompose lui-même cette requête, écrit chaque partie de fichier dans un fichier temporaire du `upload_tmp_dir`, consigne les informations dans `$_FILES` et ne démarre votre script qu'ensuite. Le script déplace le fichier avec `move_uploaded_file()` ; tout ce qui n'est pas déplacé est supprimé par PHP à la fin de la requête.

## Le trajet d'un fichier, du navigateur jusqu'au script

Pour les décideurs, un point est essentiel : un upload n'est pas une opération qui concerne la seule application. Une grande partie du travail s'effectue dans l'interpréteur PHP, avant même que le code du CMS ou de la boutique n'intervienne. C'est pourquoi les failles de cette partie de PHP touchent chaque site web, qu'il dispose ou non d'un formulaire d'upload.

Le déroulement en un coup d'œil :

1. Le formulaire avec `method="POST"` et `enctype="multipart/form-data"` est envoyé.
2. Le serveur web accepte la requête et la transmet à PHP, selon le mode de fonctionnement via PHP-FPM, mod_php ou une autre interface.
3. PHP vérifie les limites telles que `post_max_size` et découpe le contenu à l'aide d'une chaîne de séparation, appelée boundary.
4. Les champs normaux se retrouvent dans `$_POST` ; les parties de fichiers sont écrites dans des fichiers temporaires et décrites dans `$_FILES`.
5. Ce n'est qu'à ce moment que le script démarre, contrôle le fichier et le déplace vers sa destination.
6. À la fin de la requête, PHP supprime tous les fichiers temporaires qui n'ont pas été déplacés.

## multipart/form-data : la structure d'une requête d'upload

Une requête d'upload est du texte brut contenant des données de fichier intégrées. L'en-tête `Content-Type` indique la boundary, et dans le contenu, cette chaîne sépare les différentes parties. Chaque partie possède ses propres en-têtes avec le nom du champ et, le cas échéant, le nom du fichier. Un exemple inoffensif, fortement abrégé :

```http
POST /bewerbung HTTP/1.1
Content-Type: multipart/form-data; boundary=----trenner42

------trenner42
Content-Disposition: form-data; name="vorname"

Erika
------trenner42
Content-Disposition: form-data; name="lebenslauf"; filename="lebenslauf.pdf"
Content-Type: application/pdf

%PDF-1.7 ... Dateiinhalt ...
------trenner42--
```

Toutes les informations de ce texte proviennent de l'expéditeur, y compris le nom du fichier et le `Content-Type` de la partie. PHP les reprend dans `$_FILES`, mais n'en vérifie pas la vraisemblance.

## Le tableau $_FILES

Pour un champ nommé `lebenslauf`, `$_FILES['lebenslauf']` contient, selon le manuel PHP, les clés suivantes :

| Clé | Contenu | Digne de confiance ? |
|---|---|---|
| `name` | nom d'origine du fichier chez l'expéditeur | non |
| `full_path` | chemin communiqué par le navigateur, depuis PHP 8.1.0 | non |
| `type` | type MIME selon le navigateur, non vérifié par PHP | non |
| `size` | taille en octets | déterminée par PHP |
| `tmp_name` | chemin du fichier temporaire sur le serveur | attribué par PHP |
| `error` | code d'erreur du traitement de l'upload | défini par PHP |

Pour les champs dont le nom contient des crochets, par exemple `bilder[]`, chaque clé contient un tableau avec une entrée par fichier. L'application parcourt alors, par exemple, `$_FILES['bilder']['error']` et accède à `tmp_name` et `name` par le même index.

Un formulaire peut en outre envoyer un champ caché `MAX_FILE_SIZE` avant le champ de fichier. Selon le manuel, il ne s'agit que d'une indication pour le navigateur, facile à contourner. Vous ne pouvez compter que sur les limites imposées par le serveur : dans PHP, dans le serveur web et dans l'application elle-même.

## Limites : les directives php.ini pour les uploads

Ces directives pilotent le traitement des uploads. Les valeurs par défaut proviennent du manuel PHP et s'appliquent lorsqu'aucun php.ini ne les remplace.

| Directive | Valeur par défaut | Modifiable dans |
|---|---|---|
| `file_uploads` | `1` | configuration système uniquement |
| `upload_tmp_dir` | vide (répertoire temporaire du système) | configuration système uniquement |
| `upload_max_filesize` | `2M` | php.ini, pool, configuration de répertoire |
| `post_max_size` | `8M` | php.ini, pool, configuration de répertoire |
| `max_file_uploads` | `20` | php.ini, pool, configuration de répertoire |
| `enable_post_data_reading` | `1` | php.ini, pool, configuration de répertoire |

`post_max_size` doit être supérieur à `upload_max_filesize`, et `memory_limit` devrait en règle générale, selon le manuel, être supérieur à `post_max_size`. Si `upload_tmp_dir` n'est pas accessible en écriture, PHP se rabat sur le répertoire temporaire du système. Avec `open_basedir`, le répertoire temporaire doit être autorisé, sinon les uploads échouent. Les valeurs pratiques et leurs conséquences sont décrites dans l'article [Limiter judicieusement post_max_size et upload_max_filesize](https://phpsos.de/fr/blog/post-max-size-upload-limits/).

## Évaluer correctement les codes d'erreur

Le champ `error` indique ce qui s'est passé pendant le traitement. Les constantes vont de `UPLOAD_ERR_OK` (0) à `UPLOAD_ERR_EXTENSION` (8), en passant par `UPLOAD_ERR_INI_SIZE` (1), `UPLOAD_ERR_FORM_SIZE` (2), `UPLOAD_ERR_PARTIAL` (3), `UPLOAD_ERR_NO_FILE` (4), `UPLOAD_ERR_NO_TMP_DIR` (6) et `UPLOAD_ERR_CANT_WRITE` (7).

```php
<?php
declare(strict_types=1);

$datei = $_FILES['lebenslauf'] ?? null;
if ($datei === null) {
    exit('Keine Upload-Daten, eventuell wurde post_max_size überschritten.');   // Aucune donnée d'upload, post_max_size a peut-être été dépassé.
}

$meldung = match ($datei['error']) {
    UPLOAD_ERR_OK => null,
    UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => 'Datei zu groß.',
    UPLOAD_ERR_NO_FILE => 'Keine Datei gewählt.',
    default => 'Upload fehlgeschlagen.',
};

if ($meldung === null && is_uploaded_file($datei['tmp_name'])) {
    $ziel = '/srv/app/uploads/' . bin2hex(random_bytes(16)) . '.pdf';
    move_uploaded_file($datei['tmp_name'], $ziel);
}
```

L'exemple ne montre que la mécanique. Le contrôle du type de fichier, le stockage en dehors de la racine web et le blocage de l'exécution de scripts sont décrits dans le guide [Mettre en œuvre les uploads de fichiers PHP en toute sécurité](https://phpsos.de/fr/ratgeber/datei-upload-sicher/).

## Uploads et PHP2Shell : ce que signifient les mesures transitoires

Les failles PHP2Shell CVE-2026-109618 et CVE-2026-109619 concernent le traitement des requêtes dans l'interpréteur PHP. C'est pourquoi php.net cite, parmi les mesures transitoires, notamment `post_max_size = 8M` (sans uploads) ou `32M` (avec uploads), `upload_max_filesize` en dessous, ainsi que `session.upload_progress.enabled=0`. Ces valeurs relèvent du php.ini, du pool FPM ou de la configuration du serveur ; tous les workers doivent ensuite redémarrer.

Des auteurs spécialisés proposent en outre de bloquer au niveau du proxy ou du WAF les requêtes `multipart/form-data` en dehors des véritables chemins d'upload. Il s'agit d'une mesure non officielle, avec le risque de casser des formulaires ; l'article [Filtrer les requêtes d'upload en périphérie](https://phpsos.de/fr/blog/upload-anfragen-am-rand-filtern/) décrit les compromis. Aucune de ces mesures ne remplace la mise à jour vers PHP 8.5.12, 8.4.27, 8.3.36 ou 8.2.35.

## Comment PHP SOS aide-t-il concrètement ?

- Nous déterminons lesquels de vos sites ont réellement besoin d'uploads, et sur quels chemins.
- Nous définissons les limites et les mesures transitoires pool par pool, redémarrons tous les workers et testons les formulaires.
- Nous recherchons des fichiers exécutables dans les répertoires d'upload et bloquons l'exécution de scripts dans le cadre du [durcissement de serveur](https://phpsos.de/fr/leistungen/server-haertung/).
- Nous installons la mise à jour de PHP et documentons le niveau de correctifs de chaque site.


## Questions fréquentes

**Où PHP enregistre-t-il d'abord les fichiers téléversés ?**  
Dans un répertoire temporaire. Par défaut, il s'agit du répertoire temporaire du système ; upload_tmp_dir permet de le modifier. Si le script ne déplace pas le fichier, PHP le supprime à la fin de la requête.

**Puis-je me fier au champ type de $_FILES ?**  
Non. Le type MIME est communiqué par le navigateur, et selon le manuel PHP ne le vérifie pas. name et full_path proviennent eux aussi de l'expéditeur. Le type réel du fichier se détermine côté serveur, à partir du contenu.

**Pourquoi $_FILES est-il vide alors qu'un fichier a été envoyé ?**  
Le plus souvent, la requête entière dépassait post_max_size. PHP vide alors entièrement $_POST et $_FILES, sans code d'erreur dans le tableau. D'autres causes possibles : un enctype manquant dans le formulaire ou file_uploads désactivé.

**Interdire les uploads dans l'application protège-t-il contre PHP2Shell ?**  
Non. Le traitement des données de la requête a lieu dans PHP, avant l'exécution du code de l'application. Ce qui est efficace, c'est la mise à jour de PHP et le redémarrage de tous les workers ; des limites plus strictes ne sont qu'une mesure transitoire.

**Quelle est la différence entre upload_max_filesize et post_max_size ?**  
upload_max_filesize limite chaque fichier pris isolément, post_max_size l'ensemble du contenu de la requête, fichiers et champs compris. post_max_size doit donc être supérieur à upload_max_filesize.

## Sources

- [Manuel PHP : envois de fichiers par POST](https://www.php.net/manual/de/features.file-upload.post-method.php)
- [Manuel PHP : messages d'erreur lors des uploads](https://www.php.net/manual/de/features.file-upload.errors.php)
- [Manuel PHP : directives php.ini](https://www.php.net/manual/de/ini.core.php)
- [php.net : avis de sécurité](https://www.php.net/)
