# Implementar uploads de ficheiros em PHP com segurança: segurança de uploads em PHP

> Segurança de uploads em PHP na prática: verificar o tipo de ficheiro no servidor, renomear, guardar fora da raiz web e bloquear a execução de PHP.

Fonte: https://phpsos.de/pt/ratgeber/datei-upload-sicher/  
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:** na segurança de uploads em PHP não decide uma verificação isolada, mas a combinação: verificar o tipo de ficheiro no servidor, renomear os ficheiros, guardá-los fora da raiz web e não permitir a execução de scripts na pasta de uploads. Um formulário de upload sem verificação é um dos caminhos clássicos pelos quais os atacantes levam uma backdoor para um servidor web.

## Porque é que os formulários de upload são uma porta de entrada preferida

Qualquer formulário que aceite ficheiros escreve dados alheios no seu servidor. Incluem-se formulários de candidatura, portais de clientes, galerias de imagens, áreas de suporte e muitas extensões de CMS e lojas. Se um atacante conseguir colocar ali um ficheiro que o servidor web depois execute como PHP, tem uma backdoor persistente.

Para a gestão, isto significa: um upload de aparência inofensiva para currículos pode, no pior dos casos, levar à tomada de todo o servidor web. Além disso, nas pastas de uploads há muitas vezes dados pessoais que, com uma configuração errada, ficam acessíveis ao público.

Para administradores e programadores significa: o código da aplicação e a configuração do servidor têm de condizer. Uma fragilidade num nível é muitas vezes compensada pelo outro, se ambos estiverem bem implementados. Como encontrar ficheiros já introduzidos, descreve-o o guia [Detetar e remover webshells](https://phpsos.de/pt/ratgeber/webshell-erkennen/).

## As regras básicas da segurança de uploads em PHP

As regras seguintes correspondem às recomendações das OWASP Cheat Sheets e têm-se revelado eficazes em operação:

- **Lista de permissão em vez de lista de bloqueio:** aceitar apenas tipos de ficheiro expressamente permitidos, por exemplo PDF, JPG e PNG. As listas de bloqueio de extensões isoladas podem ser contornadas com variantes e extensões duplas.
- **Verificar o conteúdo, não apenas a extensão:** determinar o tipo real no servidor com `finfo`. O tipo MIME em `$_FILES` é uma indicação do remetente e não é fiável.
- **Novo nome de ficheiro:** nunca usar o nome original como nome de ficheiro. Gerar um nome aleatório e acrescentar a extensão da lista de permissão.
- **Armazenamento fora da raiz web:** guardar os ficheiros de modo que não sejam acessíveis diretamente por URL. A entrega faz-se através de um script com verificação de permissões.
- **Limitar o tamanho:** na aplicação, no PHP e no servidor web.
- **Sem execução:** se os uploads tiverem mesmo de ficar na raiz web, desativar a execução de scripts nesse diretório.
- **Verificação apenas no servidor:** as verificações em JavaScript no navegador melhoram a utilização, mas não protegem.

## Um exemplo seguro com finfo e nome aleatório

O exemplo seguinte mostra o núcleo de um processamento seguro. É deliberadamente conciso e tem de ser integrado na sua aplicação, ou seja, com autenticação, verificação de permissões, proteção CSRF e registo.

```php
<?php
declare(strict_types=1);

const MAX_BYTES  = 5 * 1024 * 1024; // 5 MB
const ERLAUBT    = [
    'application/pdf' => 'pdf',
    'image/jpeg'      => 'jpg',
    'image/png'       => 'png',
];
const ZIELORDNER = '/var/www/daten/uploads'; // fora da raiz web

$datei = $_FILES['dokument'] ?? null;

if (!is_array($datei) || $datei['error'] !== UPLOAD_ERR_OK) {
    http_response_code(400);
    exit('Upload fehlgeschlagen.');
}

if ($datei['size'] > MAX_BYTES || !is_uploaded_file($datei['tmp_name'])) {
    http_response_code(400);
    exit('Datei ungültig.');
}

$finfo = new finfo(FILEINFO_MIME_TYPE);
$typ   = $finfo->file($datei['tmp_name']);

if (!isset(ERLAUBT[$typ])) {
    http_response_code(415);
    exit('Dateityp nicht erlaubt.');
}

$name = bin2hex(random_bytes(16)) . '.' . ERLAUBT[$typ];
$ziel = ZIELORDNER . '/' . $name;

if (!move_uploaded_file($datei['tmp_name'], $ziel)) {
    http_response_code(500);
    exit('Speichern fehlgeschlagen.');
}

chmod($ziel, 0640);
// Guardar o nome original apenas como texto na base de dados
// e escapá-lo com htmlspecialchars() em cada saída.
```

Para imagens vale a pena um passo adicional: voltar a codificar a imagem com GD ou Imagick, descartando os metadados. Isso não substitui as outras verificações, mas reduz o risco residual de dados alheios incorporados.

Para documentos de fontes desconhecidas, como candidaturas ou fotos de reclamações, é útil, além disso, uma verificação antivírus antes de os reencaminhar. Protege menos o servidor web do que os colaboradores que mais tarde abrem os ficheiros nos seus computadores de trabalho. Registe ainda cada upload com hora, conta, tamanho do ficheiro e tipo detetado, para que padrões invulgares possam ser reconstituídos mais tarde.

Nos CMS e lojas, as bibliotecas de multimédia integradas trazem em geral verificações próprias. Mais arriscadas são as extensões de formulários, as funções de importação e as páginas adicionais programadas à medida, que guardam ficheiros contornando a biblioteca. São precisamente estes pontos que deve examinar primeiro numa verificação.

> **Dica:**
> Entregue os ficheiros da pasta protegida com `Content-Disposition: attachment` e `X-Content-Type-Options: nosniff`. Assim, o navegador não mostra diretamente conteúdos problemáticos, mas oferece-os como download.

## Desativar a execução de PHP na pasta de uploads

Mesmo que um ficheiro com conteúdo falso passe, o servidor web não o pode executar como script. Esta segunda camada de proteção pertence a todos os servidores que guardam uploads na raiz web.

Para Nginx com PHP-FPM:

```nginx
location ^~ /uploads/ {
    location ~* \.(php|phtml|phar)$ {
        deny all;
    }
}
```

Para Apache, de preferência diretamente no VirtualHost e não num `.htaccess`, para que a regra não possa ser substituída pela aplicação:

```apache
<Directory "/var/www/html/uploads">
    AllowOverride None
    <FilesMatch "\.(php|phtml|phar)$">
        Require all denied
    </FilesMatch>
</Directory>
```

Se o PHP correr como módulo do Apache (mod_php), pode definir adicionalmente `php_admin_flag engine off` no mesmo bloco. Depois da alteração, verifique com um ficheiro de teste inofensivo, que apenas devolve um texto, que o servidor responde a um pedido sobre ele com 403.

## Limites de tamanho e a relação com o PHP2Shell

Os uploads estão estreitamente ligados às definições PHP `post_max_size` e `upload_max_filesize`. No contexto do PHP2Shell, o php.net recomenda como medida transitória definir `post_max_size` para 8M quando não são necessários uploads, ou para 32M com uploads, e manter o `upload_max_filesize` abaixo disso. Não é uma proteção completa e não substitui a atualização para uma versão corrigida do PHP.

```ini
; php.ini ou pool FPM (php_admin_value), depois reiniciar todos os workers
file_uploads        = On
post_max_size       = 32M
upload_max_filesize = 20M
max_file_uploads    = 5
```

Se um site não precisar de uploads, o `file_uploads = Off` é a solução mais limpa. Que definições fazem sentido até ao patch, está em [Medidas imediatas sem patch](https://phpsos.de/pt/php2shell/sofortmassnahmen-ohne-patch/). Permissões adequadas para diretórios de uploads são descritas em [Permissões de ficheiros no servidor web](https://phpsos.de/pt/ratgeber/dateirechte-webserver/).

## Erros típicos nas funções de upload

Ao rever aplicações existentes, surgem repetidamente os mesmos padrões. Verifique os seus formulários de forma orientada quanto a estes pontos:

- A extensão só é verificada parcialmente, de modo que passam nomes de ficheiro com várias extensões.
- O nome original do formulário acaba no caminho de armazenamento. Isso abre caminhos para a manipulação de caminhos e para a substituição de ficheiros existentes.
- Os uploads estão públicos com nomes previsíveis. Em candidaturas ou contratos, isso é também um problema de proteção de dados.
- A pasta de uploads é gravável pelo servidor web e, ao mesmo tempo, executável.
- Extensões de upload antigas ou plugins de formulários não são atualizados, embora o fabricante tenha publicado atualizações de segurança.
- Os erros não são registados. Os uploads rejeitados são, porém, um sinal precoce de tentativas dirigidas.

## Como ajuda concretamente a PHP SOS?

- Verificamos as suas funções de upload no código e na configuração do servidor e indicamos fragilidades concretas, com prioridade.
- Aplicamos o bloqueio de execução na pasta de uploads, permissões adequadas e limites de tamanho no âmbito do [reforço de segurança do servidor](https://phpsos.de/pt/leistungen/server-haertung/).
- Em caso de suspeita, procuramos nos diretórios de uploads scripts introduzidos e limpamo-los através da nossa [remoção de malware](https://phpsos.de/pt/leistungen/malware-entfernung/).
- Se desejar, mandamos verificar a implementação, de forma independente, através de um [teste de intrusão](https://phpsos.de/pt/leistungen/sicherheitsaudit-penetrationstest/).


## Perguntas frequentes

**Basta verificar a extensão do ficheiro no upload?**  
Não. A extensão e o tipo MIME comunicado pelo navegador vêm do remetente e podem ser escolhidos livremente. Verifique o conteúdo no servidor, atribua um novo nome de ficheiro e bloqueie a execução de scripts no diretório de uploads.

**Onde devem ser guardados os ficheiros carregados?**  
O melhor é fora da pasta web pública. A entrega fica então a cargo de um script que verifica antes se o utilizador pode ver o ficheiro. Assim, os ficheiros não são nem diretamente acessíveis nem executáveis.

**Os ficheiros SVG como imagens são inofensivos?**  
Não automaticamente. O SVG é XML e pode conter conteúdo ativo. Só permita SVG se limpar os ficheiros ou se os entregar exclusivamente como download, com o cabeçalho Content-Disposition adequado.

**O tamanho do upload tem alguma relação com o PHP2Shell?**  
Sim, indiretamente. O php.net recomenda, como medida transitória, valores mais baixos para post_max_size e upload_max_filesize. Isso reduz o risco, mas não substitui a atualização para uma versão corrigida do PHP e o reinício de todos os workers.

## Fontes

- [OWASP Cheat Sheet Series (File Upload)](https://cheatsheetseries.owasp.org/)
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [php.net: avisos de segurança sobre o PHP2Shell](https://www.php.net/)
