Skip to main content

Insecure file upload

Need

Secure file upload and validation

Context

  • Usage of Java 8 for developing applications in the Java programming language
  • Usage of javax.servlet-api for developing Java servlet-based web applications
  • Usage of commons-fileupload for handling file uploads in a web application
  • Usage of commons-io for common input/output operations in Java

Description

Non compliant code

import java.io.File;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

public class UploadServlet extends HttpServlet {

private static final String UPLOAD_DIRECTORY = "upload";

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if(ServletFileUpload.isMultipartContent(request)){
try {
List<FileItem> multiparts = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(request);

for(FileItem item : multiparts){
if(!item.isFormField()){
String name = new File(item.getName()).getName();
item.write( new File(UPLOAD_DIRECTORY + File.separator + name));
}
}
} catch (Exception ex) {
request.setAttribute("message", "File Upload Failed due to " + ex);
}
}else{
request.setAttribute("message", "Sorry this Servlet only handles file upload request");
}
request.getRequestDispatcher("/result.jsp").forward(request, response);
}
}

The above code is a simple servlet for handling file uploads in a Java web application using the Apache Commons FileUpload library.

The doPost method is where the vulnerability lies. It handles the file upload request without any checks for the file type or content.

The if(ServletFileUpload.isMultipartContent(request)) statement checks if the incoming request is a multipart request, which is the type of HTTP request that is used for file uploads.

If it is a multipart request, the code proceeds to parse the request and extract the file items using List<FileItem> multiparts = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(request);

Then, for each file item, it checks if it is a form field or a file with if(!item.isFormField()). If it's a file, it gets the file name with String name = new File(item.getName()).getName(); and writes the file to the upload directory with item.write( new File(UPLOAD_DIRECTORY + File.separator + name));

The problem here is that there is no validation on the file type or content. This means that a user could upload a file with a double extension (e.g., .jpg.exe), or a file with a malicious extension (.html, .php, .exe), or even a file that contains malicious code. This could lead to various security issues, such as code execution, script injection, or even system takeover if the uploaded file is executed on the server.

Steps

  • Implement file type validation: Add a validation step to check the file extension before allowing the upload. Only allow specific file types that are considered safe.
  • Implement file content validation: Perform a thorough validation of the file content to ensure it does not contain any malicious code or scripts. Use a reliable file scanning mechanism or antivirus software to detect any potential threats.
  • Store uploaded files in a secure location: Create a separate directory outside of the web root directory to store the uploaded files. This prevents direct access to the files by users and reduces the risk of executing malicious files.
  • Rename the uploaded files: Generate a unique and random filename for each uploaded file to prevent any potential attacks that rely on predictable filenames.
  • Set appropriate file permissions: Ensure that the uploaded files have the correct file permissions set to restrict access to authorized users only.
  • Implement server-side file type checking: Use server-side code to verify the file type based on its content, in addition to the file extension. This provides an extra layer of security to prevent malicious files from being uploaded.
  • Implement file size restrictions: Set a maximum file size limit for uploads to prevent the upload of excessively large files that could consume server resources or cause denial of service attacks.
  • Log and monitor file uploads: Implement logging and monitoring mechanisms to keep track of all file uploads. This can help in identifying any suspicious activities or potential security breaches.
  • Educate users: Provide clear instructions and guidelines to users regarding the types of files that are allowed to be uploaded and the potential risks associated with file uploads.

Compliant code

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

public class UploadServlet extends HttpServlet {

private static final String UPLOAD_DIRECTORY = "secure_upload";
private static final List<String> ALLOWED_EXTENSIONS = Arrays.asList("jpg", "png", "txt");
private static final long MAX_FILE_SIZE = 1024 * 1024 * 5; // 5MB

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if(ServletFileUpload.isMultipartContent(request)){
try {
List<FileItem> multiparts = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(request);

for(FileItem item : multiparts){
if(!item.isFormField()){
String name = new File(item.getName()).getName();
String extension = name.substring(name.lastIndexOf(".") + 1);

// Check file extension
if (!ALLOWED_EXTENSIONS.contains(extension)) {
throw new Exception("File type not allowed");
}

// Check file size
if (item.getSize() > MAX_FILE_SIZE) {
throw new Exception("File size exceeds limit");
}

// Generate unique file name
String uniqueFileName = UUID.randomUUID().toString() + "." + extension;

// Write file to secure directory
File file = new File(UPLOAD_DIRECTORY + File.separator + uniqueFileName);
item.write(file);

// Set file permissions
Files.setPosixFilePermissions(Paths.get(file.getPath()), PosixFilePermissions.fromString("rwx------"));
}
}
} catch (Exception ex) {
request.setAttribute("message", "File Upload Failed due to " + ex);
}
}else{
request.setAttribute("message", "Sorry this Servlet only handles file upload request");
}
request.getRequestDispatcher("/result.jsp").forward(request, response);
}
}

The updated code includes several security measures to mitigate the risk of insecure file uploads:

  1. File Type Validation: The code checks the file extension against a list of allowed extensions before uploading the file. Only files with "jpg", "png", and "txt" extensions are allowed.

  2. File Size Restriction: The code checks the size of the file and throws an exception if the file size exceeds the maximum limit of 5MB.

  3. Unique File Name: The code generates a unique file name for each uploaded file using a UUID. This prevents potential attacks that rely on predictable file names.

  4. Secure File Storage: The uploaded files are stored in a secure directory outside of the web root directory. This prevents direct access to the files by users.

  5. File Permissions: The code sets the file permissions to "rwx------", which means that only the owner of the file has read, write, and execute permissions.

Note: This code assumes a Unix-like operating system where you can set file permissions using the Files.setPosixFilePermissions method. If you're using a different operating system, you may need to use a different method to set file permissions.

References