Conteúdo
# Security Fix: File Upload — Checklist Completo
Boa captura, Daniel. O post do `ab.zerosix` está correto. Vou te dar a implementação prática de cada ponto para o teu contexto (Nginx/Traefik + PHP ou Node).
---
## ✅ As 5 Correções do Post — Como Implementar
### 1. File Content Validation (Magic Bytes)
Não confiar na extensão. Ler os primeiros bytes do arquivo.
**PHP:**
```php
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['upload']['tmp_name']);
$allowed = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
if (!in_array($mime, $allowed)) {
die('Tipo de arquivo não permitido.');
}
```
**Node.js (com `file-type`):**
```javascript
import { fileTypeFromBuffer } from 'file-type';
const buffer = fs.readFileSync(filePath);
const type = await fileTypeFromBuffer(buffer);
const allowed = ['image/jpeg', 'image/png'];
if (!type || !allowed.includes(type.mime)) {
throw new Error('Arquivo inválido');
}
```
---
### 2. MIME Type Checking (Server-side)
Nunca confiar no `Content-Type` enviado pelo cliente.
```php
// ERRADO - cliente pode falsificar
$mime = $_FILES['file']['type']; // ❌
// CORRETO - servidor verifica
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['file']['tmp_name']); // ✅
```
---
### 3. Random File Names
```php
$ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
$ext = strtolower($ext);
// Whitelist de extensões
$allowedExt = ['jpg', 'jpeg', 'png', 'pdf'];
if (!in_array($ext, $allowedExt)) die('Extensão não permitida');
// Nome aleatório seguro
$newName = bin2hex(random_bytes(16)) . '.' . $ext;
// Ex: a3f8c12d...jpg
```
---
### 4. Disable Script Execution na pasta uploads/
**Nginx** (bloquear PHP dentro da pasta de uploads):
```nginx
location ~* /uploads/.*\.(php|php5|phtml|sh|cgi|pl)$ {
deny all;
return 403;
}
```
**Apache (.htaccess dentro de /uploads/):**
```apache
<FilesMatch "\.(php|php5|phtml|sh|cgi|pl)$">
Deny from all
</FilesMatch>
Options -ExecCGI
AddHandler cgi-script .php .php5 .phtml .sh
```
---
### 5. Store Outside Web Root
```php
// ERRADO - acessível por URL direta
$uploadDir = '/var/www/html/uploads/'; // ❌
// CORRETO - fora do web root
$uploadDir = '/var/www/storage/uploads/'; // ✅
// Servir via script controlado:
```
**Script para servir arquivo com controle:**
```php
// download.php?file=abc123.jpg
$filename = basename($_GET['file']);
$path = '/var/www/storage/uploads/' . $filename;
// Validações antes de servir
if (!file_exists($path)) die('404');
$mime = mime_content_type($path);
header('Content-Type: ' . $mime);
readfile($path);
```
---
## 🔒 Bônus: Checklist Extra para Teu Stack
```bash
# Verificar se pasta de uploads tem permissão de execução
ls -la /var/www/html/uploads/
# Remover permissão de execução
chmod -x /var/www/html/uploads/
chmod 644 /var/www/html/uploads/*
# No Docker: montar uploads como volume separado
# e garantir que o container web não tenha acesso de exec
```
### Traefik — Header de Segurança:
```yaml
# No seu middlewares do Traefik
headers:
customResponseHeaders:
X-Content-Type-Options: "nosniff" # Impede MIME sniffing
X-Frame-Options: "SAMEORIGIN"
Content-Security-Policy: "default-src 'self'"
```
---
## 📋 Resumo Rápido
| Proteção | O que previne |
|---|---|
| Magic Bytes | Arquivo `.jpg` com código PHP dentro |
| MIME server-side | Cliente forjando Content-Type |
| Nome aleatório | Path traversal + execução direta |
| Sem exec em /uploads | Webshell mesmo que o upload passe |
| Fora do web root | Acesso direto por URL ao arquivo |
---
Quer que eu faça um **script de auditoria** que roda no teu servidor e verifica automaticamente todas essas brechas no teu ambiente atual?