Description
Using a submitted filename as a storage path may let an attacker save a file in an unintended location. Names containing path traversal sequences such as ../ or ..\ can direct writes outside the intended directory.
Potential impact
- Unintended file creation: A manipulated filename may select a different storage location.
- Overwriting files: Existing files may be damaged if the write API permits replacement and the process has permission.
- Code execution: An upload may lead to code execution if the server is configured to execute it. Saving a file does not itself execute it.
Remediation
- Generate storage names on the server, such as UUID-based names, instead of using submitted names directly.
- Accept only the intended filename format. Reject empty names and special names such as
.and.., rather than trying to remove traversal characters. An allow-list pattern such as^[a-zA-Z0-9._-]+$is only one part of validation. - Keep uploads in a controlled directory such as
/var/app/uploads, and verify that storage paths stay within it.
Examples
These are javax.servlet upload-handler excerpts. Configure multipart handling, authentication, authorization, CSRF protection, size/content checks and error handling separately. Create uploads as a server-controlled directory outside executable web paths and prevent others from modifying it. A .txt suffix does not validate file content.
Before
java
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
@WebServlet("/upload")
public class FileUploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Part filePart = request.getPart("file");
String fileName = filePart.getSubmittedFileName(); // Use the submitted name directly.
Files.copy(filePart.getInputStream(), Paths.get("uploads", fileName));
}
}
After
java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
@WebServlet("/upload")
public class SecureFileUploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Part filePart = request.getPart("file");
String fileName = filePart.getSubmittedFileName();
if (fileName == null || fileName.equals(".") || fileName.equals("..") || !isFileNameAllowed(fileName)) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid file name");
return;
}
// Generate the stored name on the server.
String storedFileName = UUID.randomUUID().toString() + ".txt";
Path targetPath = Paths.get("uploads").resolve(storedFileName);
Files.copy(filePart.getInputStream(), targetPath);
}
private boolean isFileNameAllowed(String fileName) {
return fileName.matches("^[a-zA-Z0-9._-]+$"); // Check the permitted filename pattern.
}
}
Explanation:
- Before: Uses the submitted name in the storage path and may create a new file in an unintended writable location. This
Files.copy()call fails by default if the target already exists; it does not read a target file. - After: Rejects missing, empty or special names and checks the allowed pattern without relying on platform-specific separator removal. A server-generated UUID determines the actual stored filename.