Situação atual: PHP2Shell. Duas vulnerabilidades críticas do PHP (CVSS 9,2) permitem executar código malicioso sem autenticação. Patches disponíveis desde 21/10/2026.O que fazer agora →

Como o PHP processa os uploads de ficheiros

Entre o clique em enviar e o seu código há uma cadeia de processamento inteira no PHP. Esta página explica multipart/form-data, ficheiros temporários, $_FILES, limites e a relação com o PHP2Shell.

Atualizado em 5 min de leituraConhecimento PHP

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:

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.

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
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.

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. 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.
  • 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

Contacto imediato

Cada hora conta. Escreva-nos agora.

Descreva brevemente o sistema, a versão do PHP e o que observou. Entramos em contacto consigo, esclarecemos a situação e trabalhamos remotamente até o incidente estar concluído e documentado.

Ajuda imediata