# Como o PHP processa os uploads de ficheiros

> Como o PHP processa uploads de ficheiros: multipart/form-data, ficheiros temporários em upload_tmp_dir, o array $_FILES, limites e códigos de erro explicados.

Fonte: https://phpsos.de/pt/php/datei-uploads-intern/  
Atualizado em: 25/10/2026  
Prestador: PHP SOS, Assistência de emergência PHP e segurança de servidores. Ajuda imediata por WhatsApp: https://wa.me/491736494914?text=Ol%C3%A1%2C%20PHP%20SOS%2C%20precisamos%20de%20ajuda%20com%20o%20nosso%20servidor%20PHP.%20Empresa%3A%20%E2%80%A6%20Site%3A%20%E2%80%A6

**Em resumo:** Quando um navegador envia um ficheiro, faz um pedido POST no formato `multipart/form-data`, em que os campos do formulário e o conteúdo dos ficheiros surgem como partes individuais, uma a seguir à outra. O PHP decompõe ele próprio este pedido, escreve cada parte de ficheiro num ficheiro temporário em `upload_tmp_dir`, regista os dados em `$_FILES` e só então inicia o seu script. O script move o ficheiro com `move_uploaded_file()`; tudo o que não for movido, o PHP elimina no fim do pedido.

## O percurso de um ficheiro, do navegador ao script

Para quem decide, o mais importante é isto: um upload não é uma operação só da aplicação. Uma grande parte do trabalho acontece no interpretador PHP, antes de o código do CMS ou da loja intervir sequer. Por isso, os erros nesta parte do PHP afetam todos os sites, tenham ou não um formulário de upload.

O processo em resumo:

1. O formulário com `method="POST"` e `enctype="multipart/form-data"` é submetido.
2. O servidor web aceita o pedido e encaminha-o para o PHP, consoante o modo de operação através do PHP-FPM, do mod_php ou de outra interface.
3. O PHP verifica limites como `post_max_size` e decompõe o conteúdo com base numa sequência separadora, a chamada boundary.
4. Os campos normais vão para `$_POST`, as partes de ficheiro são escritas em ficheiros temporários e descritas em `$_FILES`.
5. Só agora o script arranca, verifica o ficheiro e move-o para o destino.
6. No fim do pedido, o PHP elimina todos os ficheiros temporários que não foram movidos.

## multipart/form-data: a estrutura de um pedido de upload

Um pedido de upload é texto simples com dados de ficheiro incorporados. O cabeçalho `Content-Type` indica a boundary e, no conteúdo, essa sequência separa as partes individuais. Cada parte tem os seus próprios cabeçalhos, com o nome do campo e, se for caso disso, o nome do ficheiro. Um exemplo muito abreviado e inofensivo:

```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--
```

Todos os dados deste texto vêm do remetente, incluindo o nome do ficheiro e o `Content-Type` da parte. O PHP transfere-os para `$_FILES`, mas não verifica a sua plausibilidade.

## O array $_FILES

Para um campo com o nome `lebenslauf`, o `$_FILES['lebenslauf']` contém, segundo o manual do PHP, estas chaves:

| Chave | Conteúdo | Merece confiança? |
|---|---|---|
| `name` | nome original do ficheiro no remetente | não |
| `full_path` | caminho comunicado pelo navegador, desde o PHP 8.1.0 | não |
| `type` | tipo MIME segundo o navegador, não verificado pelo PHP | não |
| `size` | tamanho em bytes | determinado pelo PHP |
| `tmp_name` | caminho do ficheiro temporário no servidor | atribuído pelo PHP |
| `error` | código de erro do processamento do upload | definido pelo PHP |

Nos campos com parênteses retos no nome, por exemplo `bilder[]`, cada chave contém um array com uma entrada por ficheiro. A aplicação percorre então, por exemplo, `$_FILES['bilder']['error']` e acede a `tmp_name` e `name` através do mesmo índice.

Um formulário pode ainda enviar, antes do campo de ficheiro, um campo oculto `MAX_FILE_SIZE`. Segundo o manual, é apenas uma indicação para o navegador, que se contorna com facilidade. Só pode confiar nos limites que o servidor impõe: no PHP, no servidor web e na própria aplicação.

## Limites: as diretivas do php.ini para uploads

Estas diretivas controlam o processamento de uploads. Os valores predefinidos provêm do manual do PHP e aplicam-se quando nenhum php.ini os substitui.

| Diretiva | Predefinição | Alterável em |
|---|---|---|
| `file_uploads` | `1` | apenas configuração do sistema |
| `upload_tmp_dir` | vazio (temp do sistema) | apenas configuração do sistema |
| `upload_max_filesize` | `2M` | php.ini, pool, configuração por diretório |
| `post_max_size` | `8M` | php.ini, pool, configuração por diretório |
| `max_file_uploads` | `20` | php.ini, pool, configuração por diretório |
| `enable_post_data_reading` | `1` | php.ini, pool, configuração por diretório |

O `post_max_size` tem de ser maior do que o `upload_max_filesize` e, segundo o manual, o `memory_limit` deve em regra ser maior do que o `post_max_size`. Se o `upload_tmp_dir` não for gravável, o PHP recorre à pasta temporária do sistema. Com `open_basedir`, a pasta temporária tem de estar autorizada, caso contrário os uploads falham. Os valores práticos e as suas consequências são descritos no artigo [Limitar post_max_size e upload_max_filesize de forma sensata](https://phpsos.de/pt/blog/post-max-size-upload-limits/).

## Avaliar corretamente os códigos de erro

O campo `error` diz o que aconteceu durante o processamento. As constantes vão de `UPLOAD_ERR_OK` (0), passando por `UPLOAD_ERR_INI_SIZE` (1), `UPLOAD_ERR_FORM_SIZE` (2), `UPLOAD_ERR_PARTIAL` (3) e `UPLOAD_ERR_NO_FILE` (4), até `UPLOAD_ERR_NO_TMP_DIR` (6), `UPLOAD_ERR_CANT_WRITE` (7) e `UPLOAD_ERR_EXTENSION` (8).

```php
<?php
declare(strict_types=1);

$datei = $_FILES['lebenslauf'] ?? null;
if ($datei === null) {
    exit('Keine Upload-Daten, eventuell wurde post_max_size überschritten.');
}

$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);
}
```

O exemplo mostra apenas a mecânica. A verificação do tipo de ficheiro, o armazenamento fora da raiz web e o bloqueio da execução de scripts são descritos no guia [Implementar uploads de ficheiros em PHP em segurança](https://phpsos.de/pt/ratgeber/datei-upload-sicher/).

## Uploads e PHP2Shell: o que significam as medidas provisórias

As vulnerabilidades PHP2Shell CVE-2026-109618 e CVE-2026-109619 dizem respeito ao processamento em torno do pedido, no interpretador PHP. Por isso, o php.net indica como medidas provisórias, entre outras, `post_max_size = 8M` (sem uploads) ou `32M` (com uploads), `upload_max_filesize` abaixo desse valor, e ainda `session.upload_progress.enabled=0`. Estes valores pertencem ao php.ini, ao pool FPM ou à configuração do servidor, e depois todos os workers têm de ser reiniciados.

Autores especializados sugerem ainda bloquear, no proxy ou na WAF, os pedidos `multipart/form-data` fora dos caminhos de upload reais. É uma medida não oficial, com o risco de estragar formulários; a ponderação é descrita no artigo [Filtrar pedidos de upload na periferia](https://phpsos.de/pt/blog/upload-anfragen-am-rand-filtern/). Nenhuma destas medidas substitui a atualização para o PHP 8.5.12, 8.4.27, 8.3.36 ou 8.2.35.

## Como ajuda concretamente a PHP SOS?

- Determinamos quais dos seus sites precisam realmente de uploads e em que caminhos.
- Definimos os limites e as medidas provisórias por pool, reiniciamos todos os workers e testamos os formulários.
- Verificamos as pastas de upload quanto a ficheiros executáveis e bloqueamos a execução de scripts no âmbito do [hardening do servidor](https://phpsos.de/pt/leistungen/server-haertung/).
- Aplicamos a atualização do PHP e documentamos o nível de patches de cada site.


## Perguntas frequentes

**Onde guarda o PHP primeiro os ficheiros enviados?**  
Numa pasta temporária. Por predefinição é a pasta temporária do sistema; com upload_tmp_dir pode ser alterada. Se o ficheiro não for movido no script, o PHP elimina-o no fim do pedido.

**Posso confiar no campo type de $_FILES?**  
Não. O tipo MIME é comunicado pelo navegador e, segundo o manual, o PHP não o verifica. Também name e full_path vêm do remetente. O tipo real do ficheiro determina-se no servidor, com base no conteúdo.

**Porque é que $_FILES está vazio, apesar de ter sido enviado um ficheiro?**  
Na maioria dos casos, o pedido completo era maior do que post_max_size. Nesse caso, o PHP esvazia por completo $_POST e $_FILES, sem código de erro no array. Outras causas são a falta de enctype no formulário ou file_uploads desativado.

**Ajuda contra o PHP2Shell proibir uploads na aplicação?**  
Não. O processamento dos dados do pedido acontece no PHP antes de o código da aplicação ser executado. O que funciona é a atualização do PHP e o reinício de todos os workers; limites mais apertados são apenas uma medida provisória.

**Qual é a diferença entre upload_max_filesize e post_max_size?**  
O upload_max_filesize limita cada ficheiro individual, o post_max_size limita todo o conteúdo do pedido, incluindo todos os ficheiros e campos. Por isso, o post_max_size tem de ser maior do que o upload_max_filesize.

## Fontes

- [Manual do PHP: uploads com POST](https://www.php.net/manual/de/features.file-upload.post-method.php)
- [Manual do PHP: mensagens de erro em uploads](https://www.php.net/manual/de/features.file-upload.errors.php)
- [Manual do PHP: diretivas do php.ini](https://www.php.net/manual/de/ini.core.php)
- [php.net: avisos de segurança](https://www.php.net/)
