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.
Imagina un escáner de llaves de hotel programado para abrir únicamente habitaciones del segundo piso. Si un huésped escribe '../../master-safe' en el teclado de la puerta, la cerradura vulnerable sube por el pasillo y abre la caja fuerte principal del gerente.
Web Application SecurityCWE-918.CWE-918Defense-in-DepthUn endpoint acepta un nombre de archivo o identificador de recurso provisto por el usuario vía parámetro HTTP.
El atacante inyecta secuencias relativas como '../', '..%2f' o rutas absolutas en el nombre del archivo.
El backend concatena el archivo al directorio base sin resolver rutas canónicas ni validar límites seguros.
El servidor lee y transmite archivos confidenciales del sistema (/etc/passwd, claves de entorno) al atacante.
// 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.