Skip to main content

Insecure file upload - Files Limit

Need

Enforce file upload limits and implement proper file management

Context

  • Usage of Java 8 for developing applications with enhanced features and performance
  • Usage of javax.servlet-api for building Java web applications with Servlets

Description

Non compliant code

import javax.servlet.http.*;
import javax.servlet.*;
import java.io.*;

public class FileUploadServlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Part filePart = request.getPart("file"); // Retrieves <input type="file" name="file">
String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString(); // MSIE fix.
InputStream fileContent = filePart.getInputStream();

byte[] buffer = new byte[fileContent.available()];
fileContent.read(buffer);

File targetFile = new File("/uploads/" + fileName);
OutputStream outStream = new FileOutputStream(targetFile);
outStream.write(buffer);

outStream.close();
}
}

The above code is a simple servlet that handles file uploads. When a POST request is made to this servlet with a file included in the request, the servlet will save that file to the /uploads directory on the server.

The vulnerability lies in the fact that there is no limit to the number of files that can be uploaded. The doPost method will accept any number of requests and save the files to the server without any checks for the number of files or the total size of all uploaded files.

Additionally, if a file is uploaded with the same name as an existing file in the /uploads directory, the existing file will not be deleted or replaced. Instead, the new file will simply be added to the directory alongside the existing file, potentially causing confusion or data loss.

This lack of limitation and control over file uploads can lead to various problems, such as filling up the server's storage space or allowing an attacker to upload malicious files.

Steps

  • Implement a limit on the number of files that can be uploaded per unit of time.
  • Delete the previous file from the server before uploading a new one.
  • Validate the file type and enforce restrictions on allowed file types.
  • Sanitize the file name to prevent directory traversal attacks.
  • Store the uploaded files in a secure location outside of the web root directory.
  • Implement proper access controls to restrict unauthorized access to the uploaded files.

Compliant code

import javax.servlet.http.*;
import javax.servlet.*;
import java.io.*;
import java.nio.file.*;
import java.util.concurrent.*;

public class FileUploadServlet extends HttpServlet {
private static final int MAX_UPLOADS_PER_UNIT_TIME = 10; // Define your limit
private static final long TIME_UNIT_IN_MS = 60000; // Define your time unit (here it's 1 minute)
private static final ConcurrentMap<String, Integer> uploadCounts = new ConcurrentHashMap<>();

public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String userIP = request.getRemoteAddr();
Integer userUploadCount = uploadCounts.getOrDefault(userIP, 0);

if (userUploadCount >= MAX_UPLOADS_PER_UNIT_TIME) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Upload limit exceeded");
return;
}

Part filePart = request.getPart("file");
String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString();

// Sanitize the file name
fileName = fileName.replaceAll("[^a-zA-Z0-9\\\\.\\\\-]", "_");

// Validate the file type
String fileType = getServletContext().getMimeType(fileName);
if (!"image/jpeg".equals(fileType) && !"image/png".equals(fileType)) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid file type");
return;
}

// Delete the previous file
File previousFile = new File("/secure_uploads/" + fileName);
if (previousFile.exists()) {
previousFile.delete();
}

// Write the new file
InputStream fileContent = filePart.getInputStream();
byte[] buffer = new byte[fileContent.available()];
fileContent.read(buffer);

File targetFile = new File("/secure_uploads/" + fileName);
OutputStream outStream = new FileOutputStream(targetFile);
outStream.write(buffer);

outStream.close();

// Update the upload count
uploadCounts.put(userIP, userUploadCount + 1);

// Schedule a task to reset the upload count after the time unit
Executors.newSingleThreadScheduledExecutor().schedule(() -> {
uploadCounts.remove(userIP);
}, TIME_UNIT_IN_MS, TimeUnit.MILLISECONDS);
}
}

The fixed code now includes a limit on the number of files that can be uploaded per unit of time. This is achieved by maintaining a ConcurrentHashMap that maps user IPs to their respective upload counts. If a user tries to upload more than the defined limit (MAX_UPLOADS_PER_UNIT_TIME) within the defined time unit (TIME_UNIT_IN_MS), the server responds with a 403 Forbidden error.

Before a new file is uploaded, the previous file with the same name is deleted from the server. This is done by checking if a file with the same name exists in the upload directory and deleting it if it does.

The file name is sanitized to prevent directory traversal attacks. This is done by replacing all characters that are not alphanumeric, dot, or hyphen with an underscore.

The file type is validated to ensure that only JPEG and PNG images can be uploaded. This is done by checking the MIME type of the file against a list of allowed MIME types.

The uploaded files are stored in a secure location (/secure_uploads/) outside of the web root directory.

Finally, a task is scheduled to reset the upload count for the user IP after the time unit. This is done using a ScheduledExecutorService that removes the user IP from the uploadCounts map after the defined time unit.

References