Upload de arquivos
Esta aula ensina como realizar upload de arquivos em PHP, abordando a superglobal $_FILES, a função move_uploaded_file, validação de tipo e tamanho, e os principais riscos de segurança envolvidos. O conteúdo inclui exemplos práticos e boas práticas para implementação segura.
O upload de arquivos é uma funcionalidade comum em aplicações web, permitindo que usuários enviem imagens, documentos, vídeos e outros tipos de arquivo para o servidor. Em PHP, essa tarefa é facilitada pelas superglobais e funções específicas, mas exige cuidados especiais com validação e segurança para evitar vulnerabilidades. Nesta aula, vamos explorar os mecanismos de upload, desde a captura dos dados do formulário até o armazenamento seguro no servidor, passando pelas principais validações e riscos.
Dominar o upload de arquivos é essencial para construir sistemas robustos, como galerias de fotos, sistemas de anexos em e-mails, plataformas de compartilhamento de documentos, entre outros. Vamos começar entendendo como o PHP recebe os arquivos enviados pelo cliente.
$_FILES
A superglobal $_FILES é um array associativo que contém todos os dados dos arquivos enviados via método POST com o enctype="multipart/form-data". Cada campo de arquivo no formulário gera uma entrada em $_FILES, que por sua vez contém subarrays com informações como nome original, tipo MIME, tamanho em bytes, nome temporário no servidor e possíveis erros.
Por exemplo, se o formulário tiver um input com name="arquivo", $_FILES['arquivo'] será um array com as chaves: 'name', 'type', 'tmp_name', 'error' e 'size'. O arquivo é inicialmente armazenado em um diretório temporário no servidor, e o script PHP deve movê-lo para um local definitivo usando move_uploaded_file().
<?php
// Exemplo de formulário HTML
?>
<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="file" name="arquivo">
<input type="submit" value="Enviar">
</form>
<?php
// upload.php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$file = $_FILES['arquivo'];
echo 'Nome original: ' . $file['name'] . '<br>';
echo 'Tipo MIME: ' . $file['type'] . '<br>';
echo 'Tamanho: ' . $file['size'] . ' bytes<br>';
echo 'Nome temporário: ' . $file['tmp_name'] . '<br>';
echo 'Código de erro: ' . $file['error'] . '<br>';
}
?>O código de erro (error) pode ser 0 (UPLOAD_ERR_OK), 1 (tamanho excede upload_max_filesize no php.ini), 2 (tamanho excede MAX_FILE_SIZE no formulário), 3 (upload parcial), 4 (nenhum arquivo enviado), 6 (falta pasta temporária), 7 (falha ao escrever no disco) ou 8 (extensão bloqueada).
move_uploaded_file
A função move_uploaded_file() é a maneira segura de mover um arquivo enviado de sua localização temporária para um destino permanente. Ela verifica se o arquivo foi realmente enviado via HTTP POST (evitando manipulação de caminhos) e, em caso afirmativo, move o arquivo. A sintaxe é: move_uploaded_file(string $filename, string $destination): bool.
O primeiro parâmetro é o caminho temporário ($_FILES['arquivo']['tmp_name']), e o segundo é o caminho de destino desejado. É importante definir um diretório de destino com permissões adequadas (geralmente 0755) e, idealmente, fora da raiz pública para evitar acesso direto.
<?php
$uploadDir = __DIR__ . '/uploads/';
$tmpName = $_FILES['arquivo']['tmp_name'];
$destino = $uploadDir . basename($_FILES['arquivo']['name']);
if (move_uploaded_file($tmpName, $destino)) {
echo "Arquivo enviado com sucesso para: $destino";
} else {
echo "Falha ao mover o arquivo.";
}
?>Observações importantes: sempre use basename() para evitar path traversal (ex.: nomes como ../../../etc/passwd). Além disso, nunca confie no nome original do arquivo para salvá-lo; gere um nome único (ex.: uniqid() ou hash) para evitar sobrescrita e problemas de segurança.
Validação (tipo, tamanho)
A validação é crucial para garantir que apenas arquivos permitidos sejam aceitos. Dois aspectos principais são o tipo MIME e o tamanho do arquivo. O tipo MIME enviado pelo cliente ($_FILES['arquivo']['type']) não deve ser confiável, pois pode ser falsificado. Em vez disso, use funções como mime_content_type() ou finfo para determinar o tipo real do arquivo no servidor.
O tamanho pode ser verificado comparando $_FILES['arquivo']['size'] com um limite definido (em bytes). Além disso, o php.ini define limites globais (upload_max_filesize, post_max_size) que devem ser respeitados. Exemplo de validação completa:
<?php
$allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
$maxSize = 2 * 1024 * 1024; // 2 MB
$file = $_FILES['arquivo'];
$errors = [];
// Verifica se houve erro no upload
if ($file['error'] !== UPLOAD_ERR_OK) {
$errors[] = "Erro no upload: código " . $file['error'];
}
// Verifica tamanho
if ($file['size'] > $maxSize) {
$errors[] = "Arquivo muito grande. Máximo permitido: 2MB.";
}
// Verifica tipo real usando finfo
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$realType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($realType, $allowedTypes)) {
$errors[] = "Tipo de arquivo não permitido: $realType";
}
if (empty($errors)) {
$destino = __DIR__ . '/uploads/' . uniqid() . '_' . basename($file['name']);
if (move_uploaded_file($file['tmp_name'], $destino)) {
echo "Upload realizado com sucesso!";
} else {
echo "Falha ao salvar o arquivo.";
}
} else {
foreach ($errors as $error) {
echo "<p>$error</p>";
}
}
?>Além do tipo e tamanho, é recomendável validar a extensão do arquivo (embora não substitua a verificação MIME) e, para imagens, usar getimagesize() para confirmar que é uma imagem válida.
Riscos de segurança
Upload de arquivos é uma das funcionalidades mais críticas em termos de segurança. Os principais riscos incluem: execução de código arbitrário (se o usuário enviar um script PHP e ele for acessível via web), path traversal (acesso a diretórios não autorizados), sobrecarga do servidor (arquivos muito grandes ou muitos arquivos), e injeção de conteúdo malicioso (como scripts em arquivos PDF ou imagens).
Para mitigar esses riscos, siga estas boas práticas: 1) Armazene os arquivos fora da raiz pública (ex.: /var/www/uploads/ em vez de /var/www/html/uploads/) e sirva-os via um script intermediário que leia e exiba o arquivo (ex.: download.php?id=123). 2) Nunca use o nome original do arquivo; gere nomes aleatórios com extensão segura (ex.: apenas extensões permitidas). 3) Valide o tipo MIME real no servidor, não confie no tipo enviado. 4) Defina limites de tamanho e quantidade de arquivos. 5) Desative a execução de scripts no diretório de uploads (via .htaccess ou configuração do servidor). 6) Utilize ferramentas de verificação de vírus se possível. 7) Implemente autenticação e autorização para uploads.
// Exemplo de .htaccess para desativar execução no diretório uploads
<IfModule mod_php7.c>
php_flag engine off
</IfModule>
<IfModule mod_php8.c>
php_flag engine off
</IfModule>
# Opcional: negar acesso a todos os arquivos
Order deny,allow
Deny from all
# Mas permitir acesso a arquivos específicos via script intermediárioOutra medida importante é verificar se o arquivo é realmente uma imagem (se for o caso) usando funções como getimagesize() ou exif_imagetype(), que também ajudam a evitar arquivos maliciosos disfarçados.
Boas práticas e observações finais
Sempre valide e sanitize os dados do upload. Utilize a função is_uploaded_file() para confirmar que o arquivo veio de um upload legítimo (embora move_uploaded_file já faça isso). Mantenha logs de uploads para auditoria. E lembre-se: a segurança é um processo contínuo; mantenha-se atualizado sobre novas vulnerabilidades e corrija configurações inadequadas.
Referências
- PHP: $_FILES - Manual
- PHP: move_uploaded_file - Manual
- PHP: Upload de arquivos - Manual
- OWASP: Unrestricted File Upload
- PHP: mime_content_type - Manual
- PHP: finfo_file - Manual
- PHP: Erros de upload - Manual
Exercícios
Crie um formulário HTML com um campo de upload de arquivo (nome="foto") e um script PHP que exiba o nome original e o tamanho do arquivo enviado.
✓ Resposta:<!-- formulario.html --> <form action="upload.php" method="post" enctype="multipart/form-data"> <input type="file" name="foto"> <input type="submit" value="Enviar"> </form> <!-- upload.php --> <?php if ($_SERVER['REQUEST_METHOD'] === 'POST') { $file = $_FILES['foto']; echo 'Nome: ' . htmlspecialchars($file['name']) . '<br>'; echo 'Tamanho: ' . $file['size'] . ' bytes'; } ?>Implemente um script que mova um arquivo enviado para o diretório 'uploads/' com um nome único (use uniqid).
✓ Resposta:<?php $uploadDir = __DIR__ . '/uploads/'; if (!is_dir($uploadDir)) { mkdir($uploadDir, 0755, true); } $tmpName = $_FILES['arquivo']['tmp_name']; $ext = pathinfo($_FILES['arquivo']['name'], PATHINFO_EXTENSION); $novoNome = uniqid() . '.' . $ext; $destino = $uploadDir . $novoNome; if (move_uploaded_file($tmpName, $destino)) { echo "Arquivo salvo como: $novoNome"; } else { echo "Falha ao mover."; } ?>Faça a validação de um upload permitindo apenas imagens JPEG e PNG de até 1 MB, usando a função finfo para verificar o tipo real.
✓ Resposta:<?php $allowedTypes = ['image/jpeg', 'image/png']; $maxSize = 1 * 1024 * 1024; // 1 MB $file = $_FILES['arquivo']; $errors = []; if ($file['error'] !== UPLOAD_ERR_OK) { $errors[] = "Erro no upload."; } if ($file['size'] > $maxSize) { $errors[] = "Arquivo muito grande (máx 1MB)."; } $finfo = finfo_open(FILEINFO_MIME_TYPE); $realType = finfo_file($finfo, $file['tmp_name']); finfo_close($finfo); if (!in_array($realType, $allowedTypes)) { $errors[] = "Tipo não permitido: $realType"; } if (empty($errors)) { $destino = __DIR__ . '/uploads/' . uniqid() . '_' . basename($file['name']); move_uploaded_file($file['tmp_name'], $destino); echo "Upload OK"; } else { foreach ($errors as $e) echo "<p>$e</p>"; } ?>Explique por que é perigoso confiar no tipo MIME enviado pelo cliente ($_FILES['arquivo']['type']) e como mitigar esse risco.
✓ Resposta:O tipo MIME enviado pelo cliente pode ser facilmente falsificado (por exemplo, um script PHP pode ser enviado com type image/jpeg). Para mitigar, deve-se usar funções do servidor como finfo_file() ou mime_content_type() para determinar o tipo real do arquivo. Além disso, nunca confie na extensão do arquivo; valide o conteúdo.Liste três medidas de segurança essenciais ao implementar upload de arquivos em produção.
✓ Resposta:1) Armazenar arquivos fora da raiz pública e servi-los via script intermediário. 2) Validar o tipo real do arquivo no servidor (finfo). 3) Gerar nomes únicos e seguros (uniqid) e desativar execução de scripts no diretório de uploads.