flawopen.com/path-traversal/Rust
How PathBuf::join allows directory escapes in Rust, and how to enforce containment using std::fs::canonicalize and starts_with.
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.
Web Application SecurityCWE-918.CWE-918Defense-in-DepthUm endpoint aceita um nome de arquivo ou identificador de recurso fornecido pelo usuário por parâmetro HTTP.
O invasor injeta sequências de diretório relativo como '../', '..%2f' ou caminhos absolutos no parâmetro.
O backend concatena o nome sem resolver o caminho canônico nem validar que o destino permaneça no diretório base.
O runtime abre e transmite arquivos sensíveis do sistema (como /etc/passwd ou credenciais) diretamente ao cliente.
// 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)
}
// 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)
}
resolved.is_file() before reading.