flawopen.com/path-traversal/Rust

● CWE-918 · Alta
Pesquisa · FlawOpen

Path Traversal in Rust

How PathBuf::join allows directory escapes in Rust, and how to enforce containment using std::fs::canonicalize and starts_with.

💡 Explicação em Linguagem Simples (ELI5)

Imagine um scanner de chave de hotel que só deveria abrir quartos no segundo andar. Se um hóspede digita '../../master-safe' no teclado da porta, uma fechadura vulnerável sobe pelo corredor e abre o cofre principal da gerência.

Conceitos Centrais e Termos

Web Application Security
Componente de arquitetura principal afetado pelo CWE-918.
CWE-918
Classificação padrão Common Weakness Enumeration (CWE) para path-traversal-rust.
Defense-in-Depth
Verificação de engenharia em múltiplas camadas e isolamento de limites em tempo de execução.

Fluxo de Ataque Passo a Paso

Step 1

Entrada de Caminho de Arquivo do Cliente

Um endpoint aceita um nome de arquivo ou identificador de recurso fornecido pelo usuário por parâmetro HTTP.

Step 2

Injeção de Sequência de Travessia

O invasor injeta sequências de diretório relativo como '../', '..%2f' ou caminhos absolutos no parâmetro.

Step 3

Violação do Limite do Sistema de Arquivos

O backend concatena o nome sem resolver o caminho canônico nem validar que o destino permaneça no diretório base.

Step 4

Divulgação ou Sobrescrita Arbitrária de Arquivos

O runtime abre e transmite arquivos sensíveis do sistema (como /etc/passwd ou credenciais) diretamente ao cliente.

Código-Fonte: Vulnerável vs. Seguro

✕ IMPLEMENTAÇÃO VULNERÁVEL
// VULNERABLE: PathBuf::join allows directory traversal
use std::fs;
use std::path::PathBuf;

fn read_user_file(filename: &str) -> Result<Vec<u8>, std::io::Error> {
    let base_dir = PathBuf::from("/var/app/public/files");
    // Attacker input: "../../etc/passwd"
    let target = base_dir.join(filename);
    
    // Reads arbitrary system files!
    fs::read(target)
}
✓ PATCH SEGURO E ROBUSTO
// HARDENED: Canonicalize path and assert directory prefix
use std::fs;
use std::path::{Path, PathBuf};
use std::io::{Error, ErrorKind};

fn read_user_file(filename: &str) -> Result<Vec<u8>, Error> {
    let base_dir = Path::new("/var/app/public/files").canonicalize()?;
    
    // 1. Join and canonicalize target path (resolves .. and symlinks)
    let target = base_dir.join(filename);
    let resolved = target.canonicalize()?;
    
    // 2. Strict boundary check: resolved must start with base_dir
    if !resolved.starts_with(&base_dir) {
        return Err(Error::new(ErrorKind::PermissionDenied, "Path traversal attempt detected"));
    }
    
    if !resolved.is_file() {
        return Err(Error::new(ErrorKind::NotFound, "File not found"));
    }
    
    fs::read(resolved)
}

Lista de Verificação de Segurança para Engenharia

References