flawopen.com/path-traversal/Kotlin
How unvalidated file paths in Kotlin/Ktor allow directory breakouts, and how to verify boundaries using Path.normalize and Path.startsWith.
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: Direct File instantiation with user parameter
import java.io.File;
import javax.servlet.http.*;
public class FileServlet extends HttpServlet {
private static final File BASE_DIR = new File("/var/app/public/files");
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
String filename = req.getParameter("file");
// Attacker sends: ../../../../etc/passwd
File target = new File(BASE_DIR, filename);
// Directly serves arbitrary system file!
serveFile(target, resp);
}
}
// HARDENED: Canonicalize file and enforce directory prefix check
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import javax.servlet.http.*;
public class FileServlet extends HttpServlet {
private static final File BASE_DIR = new File("/var/app/public/files");
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String filename = req.getParameter("file");
if (filename == null || filename.isBlank()) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
// 1. Resolve canonical path (resolves .. and all symlinks)
File target = new File(BASE_DIR, filename).getCanonicalFile();
File canonicalBase = BASE_DIR.getCanonicalFile();
// 2. Strict boundary check using Java NIO Path
if (!target.toPath().startsWith(canonicalBase.toPath())) {
resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Path traversal detected");
return;
}
if (!target.isFile()) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
serveFile(target, resp);
}
}
getCanonicalFile() on both target and base directory.target.toPath().startsWith(base.toPath()).target.isFile() to prevent reading device nodes or directories.ZipEntry.getName() before writing.