Boas práticas de segurança em uploads e processamento de arquivos
A funcionalidade de upload de arquivos é uma das portas de entrada mais exploradas por atacantes em aplicações web. Um sistema mal configurado pode permitir desde a execução remota de código até ataques de negação de serviço. Este artigo apresenta as principais práticas para garantir a segurança no upload e processamento de arquivos, abordando desde a validação inicial até a entrega segura ao cliente.
1. Validação rigorosa do tipo e conteúdo do arquivo
A primeira linha de defesa começa antes mesmo de o arquivo ser salvo no servidor. A validação deve ocorrer exclusivamente no lado servidor, jamais confiando em verificações feitas pelo cliente.
Verificação de extensão com whitelist
Utilize listas de permissões (whitelist) em vez de listas de bloqueio (blacklist). Enquanto uma blacklist tenta prever todas as extensões maliciosas, uma whitelist define exatamente o que é aceito.
# Exemplo de whitelist de extensões
EXTENSOES_PERMITIDAS = {'.jpg', '.jpeg', '.png', '.gif', '.pdf', '.docx'}
def validar_extensao(nome_arquivo):
extensao = os.path.splitext(nome_arquivo)[1].lower()
if extensao not in EXTENSOES_PERMITIDAS:
raise ValueError("Tipo de arquivo não permitido")
Análise de magic bytes e MIME
A extensão do arquivo pode ser facilmente falsificada. Verifique os magic bytes (assinatura de arquivo) e o cabeçalho MIME real:
# Verificação de magic bytes para JPEG
def verificar_magic_bytes(caminho_arquivo):
with open(caminho_arquivo, 'rb') as f:
cabecalho = f.read(4)
# JPEG inicia com FF D8 FF
if cabecalho[:3] != b'\xff\xd8\xff':
raise ValueError("Arquivo não é um JPEG válido")
Validação de tamanho e integridade
Defina limites máximos de tamanho e implemente verificação de integridade:
TAMANHO_MAXIMO = 10 * 1024 * 1024 # 10 MB
def validar_tamanho(arquivo):
if arquivo.tamanho > TAMANHO_MAXIMO:
raise ValueError("Arquivo excede o tamanho máximo permitido")
2. Armazenamento seguro e isolamento de arquivos
O local onde os arquivos são armazenados é tão crítico quanto as validações iniciais.
Diretórios fora da raiz pública
Nunca salve arquivos dentro do diretório público da aplicação web. Crie diretórios isolados com permissões restritas:
DIRETORIO_UPLOADS = "/var/data/uploads/" # Fora do document root do servidor web
Nomes únicos e aleatórios
Substitua o nome original do arquivo por identificadores únicos para evitar conflitos e ataques de path traversal:
import uuid
import os
def gerar_nome_seguro(extensao):
nome_unico = str(uuid.uuid4())
return f"{nome_unico}{extensao}"
caminho_final = os.path.join(DIRETORIO_UPLOADS, gerar_nome_seguro('.jpg'))
Permissões restritas
Configure permissões mínimas necessárias no sistema de arquivos:
# Permissões: apenas o proprietário pode ler/escrever
os.chmod(caminho_final, 0o600)
3. Sanitização contra injeção e execução maliciosa
Arquivos aparentemente inofensivos podem conter código malicioso oculto.
Remoção de metadados executáveis
Imagens podem conter scripts em metadados EXIF. Utilize bibliotecas especializadas para limpeza:
# Exemplo com Pillow (Python)
from PIL import Image
def sanitizar_imagem(caminho_origem, caminho_destino):
imagem = Image.open(caminho_origem)
# Salva sem metadados EXIF
imagem.save(caminho_destino, format='JPEG', exif=b'')
Escaneamento com antivírus
Para ambientes críticos, integre um scanner de malware:
# Exemplo com ClamAV
import subprocess
def escanear_arquivo(caminho):
resultado = subprocess.run(['clamscan', '--no-summary', caminho],
capture_output=True, text=True)
if 'FOUND' in resultado.stdout:
raise ValueError("Arquivo infectado detectado")
Prevenção de path traversal
Valide caminhos absolutos e relativos para evitar que o usuário acesse diretórios não autorizados:
def validar_caminho_seguro(caminho_base, nome_arquivo):
caminho_completo = os.path.normpath(os.path.join(caminho_base, nome_arquivo))
if not caminho_completo.startswith(caminho_base):
raise ValueError("Tentativa de path traversal detectada")
return caminho_completo
4. Processamento seguro de arquivos no servidor
O processamento de arquivos (conversão, redimensionamento) deve ser feito com ferramentas seguras e atualizadas.
Uso de bibliotecas confiáveis
Prefira bibliotecas mantidas ativamente e com histórico de correções de segurança:
# Processamento de PDF com PyMuPDF (fitz)
import fitz
def processar_pdf_seguro(caminho_origem, caminho_destino):
doc = fitz.open(caminho_origem)
# Remove JavaScript e ações incorporadas
for pagina in doc:
pagina.clean_contents()
doc.save(caminho_destino)
doc.close()
Limitação de recursos
Evite ataques DoS limitando memória e tempo de CPU:
import resource
import signal
def processar_com_limites(caminho_arquivo):
# Limita tempo de CPU para 30 segundos
signal.alarm(30)
# Limita memória para 256 MB
resource.setrlimit(resource.RLIMIT_AS, (256*1024*1024, 256*1024*1024))
# Processamento do arquivo...
Recompressão de arquivos
Converter e recompactar arquivos pode remover conteúdo malicioso oculto:
# Recompressão de imagem PNG para JPEG
from PIL import Image
def reconverter_imagem(caminho_origem, caminho_destino):
img = Image.open(caminho_origem).convert('RGB')
img.save(caminho_destino, 'JPEG', quality=85)
5. Controle de acesso e autenticação no upload
Todo upload deve estar vinculado a um usuário autenticado e autorizado.
Autenticação forte e validação de permissões
def verificar_permissao_upload(usuario, tipo_arquivo):
if not usuario.esta_autenticado():
raise PermissionError("Usuário não autenticado")
if tipo_arquivo not in usuario.tipos_permitidos:
raise PermissionError("Tipo de arquivo não autorizado para este usuário")
Rate limiting por usuário e IP
Implemente limites de requisições para evitar abusos:
# Exemplo com Redis para rate limiting
import redis
import time
cliente_redis = redis.Redis()
def verificar_rate_limit(usuario_id, ip):
chave = f"upload:{usuario_id}:{ip}"
tentativas = cliente_redis.incr(chave)
if tentativas == 1:
cliente_redis.expire(chave, 60) # Expira em 60 segundos
if tentativas > 10:
raise Exception("Limite de uploads excedido. Tente novamente mais tarde.")
Logs detalhados
Registre todas as operações para auditoria:
import logging
logger = logging.getLogger('upload_seguranca')
def registrar_upload(usuario_id, nome_arquivo, tamanho, sucesso):
logger.info(f"Upload: usuario={usuario_id}, arquivo={nome_arquivo}, "
f"tamanho={tamanho}, sucesso={sucesso}")
6. Proteção contra ataques de negação de serviço (DoS)
Uploads maliciosos podem sobrecarregar o servidor. Implemente proteções em múltiplas camadas.
Limitação por sessão e intervalo
TOTAL_MAXIMO_POR_SESSAO = 100 * 1024 * 1024 # 100 MB
INTERVALO_RESET = 3600 # 1 hora
def verificar_cota_sessao(sessao_id, tamanho_arquivo):
total_atual = cache.get(f"cota:{sessao_id}", 0)
if total_atual + tamanho_arquivo > TOTAL_MAXIMO_POR_SESSAO:
raise Exception("Cota de upload excedida para esta sessão")
cache.incrby(f"cota:{sessao_id}", tamanho_arquivo)
cache.expire(f"cota:{sessao_id}", INTERVALO_RESET)
Filas de processamento assíncrono
Evite processar arquivos grandes na mesma thread da requisição HTTP:
# Exemplo com Celery
from celery import Celery
app = Celery('processamento', broker='redis://localhost:6379')
@app.task
def processar_arquivo_assincrono(caminho_arquivo, usuario_id):
try:
sanitizar_imagem(caminho_arquivo, caminho_final)
registrar_upload(usuario_id, caminho_final, True)
except Exception as e:
registrar_upload(usuario_id, caminho_arquivo, False, str(e))
Timeouts e limites de concorrência
Configure o servidor web para limitar conexões simultâneas e tempo de processamento:
# Configuração Nginx para limitar uploads
client_max_body_size 10M;
client_body_timeout 30s;
proxy_read_timeout 60s;
limit_req_zone $binary_remote_addr zone=upload:10m rate=5r/s;
7. Entrega segura de arquivos ao cliente
A entrega de arquivos também deve ser protegida para evitar acesso não autorizado.
Proxy intermediário para downloads
Nunca sirva arquivos diretamente do diretório de uploads. Utilize um script intermediário que valide permissões:
def servir_arquivo(arquivo_id, usuario):
caminho_real = obter_caminho_por_id(arquivo_id)
if not usuario.tem_acesso(arquivo_id):
raise PermissionError("Acesso negado ao arquivo")
response = FileResponse(caminho_real, as_attachment=True)
response['Content-Disposition'] = f'attachment; filename="{arquivo_id}.jpg"'
return response
Cabeçalhos HTTP corretos
Configure cabeçalhos para evitar execução de conteúdo malicioso no navegador:
# Headers de segurança para download
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="arquivo_seguro.pdf"
X-Content-Type-Options: nosniff
Cache-Control: no-store
Links temporários com expiração
Implemente tokens de acesso temporários para downloads:
import hmac
import hashlib
import time
def gerar_link_temporario(arquivo_id, usuario_id, expiracao=3600):
timestamp = int(time.time()) + expiracao
mensagem = f"{arquivo_id}:{usuario_id}:{timestamp}"
token = hmac.new(SECRET_KEY.encode(), mensagem.encode(), hashlib.sha256).hexdigest()
return f"/download/{arquivo_id}?token={token}&exp={timestamp}"
def validar_link_temporario(arquivo_id, token, timestamp, usuario_id):
if int(time.time()) > int(timestamp):
return False
mensagem = f"{arquivo_id}:{usuario_id}:{timestamp}"
token_esperado = hmac.new(SECRET_KEY.encode(), mensagem.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(token, token_esperado)
Conclusão
A segurança em uploads e processamento de arquivos exige uma abordagem em camadas, combinando validação rigorosa, armazenamento isolado, sanitização de conteúdo, controle de acesso e proteção contra DoS. Cada camada funciona como uma barreira adicional que dificulta a exploração de vulnerabilidades.
Implementar todas essas práticas reduz significativamente o risco de ataques como execução remota de código, path traversal, upload de malware e negação de serviço. A segurança nunca é um estado final, mas um processo contínuo de atualização e monitoramento.
Referências
- OWASP File Upload Cheat Sheet — Guia completo da OWASP com todas as práticas recomendadas para upload seguro de arquivos
- PortSwigger - File upload vulnerabilities — Tutoriais interativos sobre vulnerabilidades comuns em upload de arquivos e como mitigá-las
- Mozilla Developer Network - Content-Disposition header — Documentação oficial sobre cabeçalhos HTTP para entrega segura de arquivos
- ClamAV Documentation - Scanning Files — Guia oficial de integração do antivírus ClamAV para escaneamento de arquivos em servidores
- Python Pillow Library - Security Considerations — Documentação de segurança da biblioteca Pillow para processamento seguro de imagens