Path traversal: prevenindo acesso a arquivos não autorizados

1. O que é Path Traversal e por que é perigoso?

Path Traversal (ou Directory Traversal) é um tipo de ataque cibernético onde o invasor manipula parâmetros de entrada que definem caminhos de arquivos para acessar diretórios e arquivos restritos no servidor. A técnica explora a falta de validação adequada de entrada do usuário, permitindo que o atacante "escape" do diretório base da aplicação.

O exemplo clássico utiliza sequências de ../ para navegar pela estrutura de diretórios:

GET /files?path=../../../etc/passwd HTTP/1.1
Host: exemplo.com

Em sistemas Windows, as variações podem incluir ..\ ou combinações de encoding como %2e%2e%2f (URL encoding de ../) ou ..%252f (double encoding).

O impacto de um Path Traversal bem-sucedido pode ser devastador:
- Vazamento de credenciais: acesso a arquivos como /etc/passwd ou config.php com senhas de banco de dados
- Exposição de código-fonte: revelação de lógica de negócios e vulnerabilidades adicionais
- Execução remota de comandos: quando combinado com outras falhas (ex: upload de arquivos maliciosos)
- Acesso a dados sensíveis: arquivos de log, backups, arquivos temporários

2. Como identificar vulnerabilidades de Path Traversal no código

Os padrões mais comuns de entrada do usuário que podem ser explorados incluem:

  • Parâmetros de URL: ?file=relatorio.pdf ou ?path=/uploads/
  • Cabeçalhos HTTP: Referer, X-Forwarded-For
  • Uploads de arquivos: nomes de arquivo com caminhos relativos
  • APIs RESTful: endpoints que recebem caminhos de arquivos como parâmetros

As falhas típicas no código são:

// VULNERÁVEL: concatenação direta
$filename = $_GET['file'];
$content = file_get_contents('/var/www/files/' . $filename);

// VULNERÁVEL: falta de normalização
string path = Path.Combine(basePath, userInput);
File.ReadAllText(path);

// VULNERÁVEL: uso de funções perigosas
import os
filename = request.args.get('file')
with open(f'/app/data/{filename}', 'r') as f:
    data = f.read()

Para identificar essas vulnerabilidades, utilize:
- Revisão manual de código: procure por operações de arquivo usando entrada não validada
- Testes de penetração: envie payloads como ../../../etc/passwd, ....//....//etc/passwd, ..%252f..%252f..%252fetc%252fpasswd
- Ferramentas automatizadas: scanners como OWASP ZAP, Burp Suite, ou linters de segurança

3. Mecanismos de prevenção: validação de entrada

A validação de entrada deve seguir o princípio de whitelist (lista de permitidos) em vez de blacklist, pois blacklists são facilmente contornáveis com variações de encoding.

Exemplo de whitelist de caracteres:

// PHP - Whitelist de caracteres permitidos
$allowedChars = '/^[a-zA-Z0-9_\-\.]+$/';
if (!preg_match($allowedChars, $filename)) {
    die("Nome de arquivo inválido");
}

Validação de extensões contra conjunto fixo:

// Python - Validação de extensões
ALLOWED_EXTENSIONS = {'.pdf', '.txt', '.jpg', '.png'}
filename = request.args.get('file')
if not any(filename.endswith(ext) for ext in ALLOWED_EXTENSIONS):
    abort(400, "Extensão não permitida")

Rejeição de caracteres suspeitos:

// C# - Rejeição de caracteres perigosos
string[] dangerousPatterns = { "..", "/", "\\", "%00", ":", "*", "?" };
foreach (var pattern in dangerousPatterns) {
    if (userInput.Contains(pattern)) {
        throw new SecurityException("Entrada inválida");
    }
}

4. Normalização e saneamento de caminhos

Após a validação inicial, é crucial normalizar o caminho para garantir que ele não aponte para fora do diretório permitido.

Exemplo em PHP com realpath():

// PHP - Normalização segura
$basePath = '/var/www/files/';
$userPath = $_GET['file'];

// Concatena e normaliza
$fullPath = realpath($basePath . $userPath);
$baseReal = realpath($basePath);

// Verifica se o caminho normalizado começa com o diretório base
if (strpos($fullPath, $baseReal) !== 0) {
    die("Acesso negado");
}

Exemplo em Python com os.path.realpath():

# Python - Verificação de diretório base
import os

BASE_DIR = '/var/www/files/'
user_input = request.args.get('file')
full_path = os.path.realpath(os.path.join(BASE_DIR, user_input))

if not full_path.startswith(os.path.realpath(BASE_DIR)):
    abort(403, "Acesso negado")

Exemplo em C# com Path.GetFullPath():

// C# - Verificação de diretório base
string basePath = @"C:\app\files\";
string userInput = Request.QueryString["file"];
string fullPath = Path.GetFullPath(Path.Combine(basePath, userInput));

if (!fullPath.StartsWith(Path.GetFullPath(basePath))) {
    throw new UnauthorizedAccessException("Acesso negado");
}

5. Boas práticas de armazenamento e acesso a arquivos

A abordagem mais segura é nunca expor caminhos reais de arquivos ao cliente. Em vez disso, utilize identificadores seguros:

Mapeamento de IDs para arquivos:

# Tabela no banco de dados
# id | filename          | path
# 1  | relatorio.pdf     | /var/storage/files/abc123.pdf
# 2  | foto_perfil.jpg   | /var/storage/images/def456.jpg

# Código seguro
file_id = request.args.get('id')
file_record = db.query("SELECT path FROM files WHERE id = ?", file_id)
if file_record:
    serve_file(file_record.path)
else:
    abort(404)

Armazenamento fora da raiz pública:

# Configuração do servidor web (Nginx)
location /files/ {
    internal;  # Bloqueia acesso direto
    alias /var/storage/files/;
}

# Apenas o backend pode servir arquivos via script PHP/Python

Nunca exponha caminhos reais ao cliente:

# ERRADO: retornar caminho real
GET /download?file=/var/www/uploads/relatorio.pdf

# CERTO: usar token ou ID
GET /download?id=12345&token=abc123def456

6. Configuração do ambiente e hardening do servidor

Medidas de hardening reduzem o impacto mesmo se uma vulnerabilidade existir:

Desabilitação de listagem de diretórios:

# Apache
Options -Indexes

# Nginx
autoindex off;

Permissões mínimas (princípio do menor privilégio):

# O usuário do servidor web deve ter acesso apenas ao necessário
chown -R www-data:www-data /var/www/app
chmod -R 755 /var/www/app
chmod 644 /var/www/app/config.php  # Apenas leitura
chmod 700 /var/www/app/keys/       # Restrito

Isolamento com chroot jails ou contêineres:

# Docker: isolar o serviço em um contêiner
FROM python:3.9-slim
WORKDIR /app
COPY app.py .
RUN useradd -m appuser
USER appuser
CMD ["python", "app.py"]

7. Testes e monitoramento contínuo

Casos de teste automatizados:

# Teste unitário (Python)
def test_path_traversal_prevention():
    app = create_app()
    with app.test_client() as client:
        # Teste com path traversal
        resp = client.get('/files?path=../../../etc/passwd')
        assert resp.status_code == 403

        # Teste com double encoding
        resp = client.get('/files?path=%2e%2e%2f%2e%2e%2fetc/passwd')
        assert resp.status_code == 403

        # Teste com caminho válido
        resp = client.get('/files?path=relatorio.pdf')
        assert resp.status_code == 200

Logging e alertas para tentativas suspeitas:

# Python - Logging de tentativas suspeitas
import logging

def serve_file(filename):
    if '..' in filename or '/' in filename:
        logging.warning(f"Tentativa de path traversal: {filename}")
        # Alertar equipe de segurança
        send_alert(f"Path traversal detectado: {filename} de IP {request.remote_addr}")
        abort(403)

Revisão periódica de dependências:

# Verificar bibliotecas de manipulação de arquivos
pip audit  # Python
npm audit  # Node.js
composer audit  # PHP

Referências