Creating a simple file upload/download application with Vaadin Flow

Vaadin Flow is a robust framework for building modern web applications in Java, where all UI logic is implemented on the server side. In this blog post, we’ll make a simple file management application step by step that allows users to upload files, save them to the server, and download them again when needed. This is a great way to demonstrate how to build protection against CWE-22, CWE-377, and CWE-778 step by step.

In this example, we’re focusing exclusively on functionality rather than on graphic design. The latter has been intentionally kept very simple to focus on the technical aspects.

The source texts for this article can be found at: https://github.com/Java-Publications/Blog—Secure-Coding-Practices—CWE-022–377–778—A-practical-Demo 

Basic project structure

To begin, we will create a new Vaadin project. The easiest way to do this is via the project starter, which you can find at https://start.vaadin.com/ or by using an existing Maven template. The file structure of our project essentially looks like this:

Screenshot

The file `MainView.java` will be the application’s central entry point. Here, we will implement the user interface and the logic for file uploads and downloads.

Add file upload functionality.

First, we will create a simple user interface that allows users to upload files. In `MainView.java`, our basic setup looks like this:

In this code, we use `MemoryBuffer` to temporarily save the uploaded file and then write it to the `uploads/` directory. If the target directory doesn’t already exist, it will be created automatically. Using `MemoryBuffer` allows easy and secure file management before it is written to the hard disk.

Add download functionality

To list the downloadable files, we’ll add a button that allows users to download any file from the directory. This improves the user experience and ensures users can access their uploaded files easily. Here we’ll extend the user interface:

Using the method `updateFileList()` displays the files stored in the `uploads/` directory as a list, and creates an `anchor` element for each file, which serves as a download link. This makes the interface more intuitive and allows users to manage uploaded files easily.

Best Practices uses Java NIO.

We can use Java NIO (New Input/Output) instead of traditional IO streams to improve efficiency and security when handling files. Java NIO provides non-blocking IO operations, enabling better performance and scalability. It also supports more flexible and secure file system operations.

We adapt our code to use Java NIO classes like `Files` and `Path` to save the uploaded files. Here’s an improved version of the upload code that uses Java NIO:

This version uses the `Files` and `Paths` classes to create directories and store files. This improves code readability and maintainability and leverages the advantages of the NIO classes, such as better exception handling and more flexible path operations. Using Java NIO makes file operations more efficient and secure, especially when working with multiple threads concurrently.

CWE-22: Path Traversal and Protection Measures

CWE-22, also known as “Path Traversal,” is a vulnerability that occurs when users can access unauthorised files and directories in the file system via insecure path names. This is typically done by users inserting special character strings such as `../` into filenames to extend beyond the boundaries of the permitted directory. If this is not controlled correctly, an attacker could gain access to critical system files, potentially leading to a serious security compromise.

In our file management application, path traversal is risky if the filename is used directly and without verification to save or retrieve a file in the file system. Attackers could attempt to manipulate path information and overwrite or read files outside the intended directory.

To avoid vulnerabilities like CWE-22 (Path Traversal) in your application, you should be especially careful with user-supplied filenames. Attackers could attempt to access files outside their intended location using manipulated path names—for example, by entering something like../../etc/passwd. Therefore, checking and cleaning all paths and file names is essential before using them. Here are a few best practices you should implement:

  • Path cleanup: Use methods like Path.normalize()from the Java NIO package to clean paths. This automatically removes dangerous constructs such as double slashes or relative elements (..) that attackers could use to traverse directories.
  • Directory restriction: Make sure that the path you use to save files is within a secure, predefined home directory – e.g. uploads/ You can verify this by comparing the cleaned destination path with the expected base directory. You should allow the upload only if the destination path is truly within the allowed range.
  • File name validation: It’s not enough to check the path alone. The filename itself can also contain dangerous characters. It’s best to allow only simple, non-critical characters, such as letters, numbers, hyphens, and periods. Using a regular expression like[a-za-Z0-9._—], you can specifically remove or replace everything else.

Considering these points makes it significantly more difficult for attackers to control paths and protect your server structure and users’ sensitive data.

Here is a customised version of our application that implements protections against CWE-22:

In this updated version of the application, we’ve added a `sanitizeFileName()` method to ensure the filename doesn’t contain any dangerous characters. We also normalise the path with `Path.normalize()` and verify that the final path is within the desired `uploads` directory. If the path is outside this directory, the upload is aborted and an appropriate error message is displayed.

These changes ensure that attackers cannot gain unauthorised access to the file system by manipulating file names. This keeps the application secure and protects both the server and the data stored on it from misuse.

CWE-377: Insecure temporary files

CWE-377, also known as “Insecure Temporary File,” describes a security vulnerability that occurs when you create temporary files in a way that can be exploited by attackers. Such files are often used to cache content, either during processing or for temporary storage in general. However, if you create them insecurely, attackers could access, tamper with, or even overwrite them.

A typical attack scenario: An attacker creates a symbolic link (symlink) pointing to a critical system file – your application then unknowingly writes data there. Or they manipulate the file while it’s still being processed, thereby introducing unwanted content or malicious code into the system. The consequences can range from data loss and integrity violations to a complete system compromise.

Temporary files are also created in your file management application—for example, when processing uploads. Therefore, you must create these files securely. Here are a few best practices to effectively avoid CWE-377:

  • Safe methods for creating temporary files:In Java, use the Files.createTempFile() method. It automatically creates a temporary file with a unique, random name, reducing the risk of attackers accessing it.
  • Restrict access rights: Only your process can access the temporary file. Set the file permissions so no other users or services have access—this is especially important in shared environments.
  • Use unpredictable file names: Avoid using fixed or easily guessed names for temporary files. Otherwise, attackers could create a file with the same name beforehand or overwrite existing ones.

Considering these points, you can securely handle temporary files and protect your application from an often underestimated attack vector.

In this revised version, you create a secure temporary file with Files.createTempFile(). You use this to temporarily store the uploaded content before moving it to the final destination directory, ensuring that third parties cannot tamper with the temporary file.

Only after the content has been successfully saved do you move the file to the correct location in the file system. This way, you can upload the files in a controlled and secure manner and minimise the risk of temporary files becoming a gateway for attackers.

CWE-778: Insufficient logging

CWE-778, also known as “Insufficient Logging,” describes a security vulnerability that occurs when your application doesn’t log enough to detect and track security-relevant events. If you don’t maintain detailed logging, attempted attacks, unauthorised access, or system errors may go unnoticed for a long time, or you may not have enough information to respond appropriately in an emergency.

Especially in safety-critical applications, you should document all critical actions and errors to understand what happened later clearly. This is the only way to allow yourself or your team to respond quickly to incidents and learn from them.

If you log too little or nothing at all, you may miss signs of attacks, such as suspicious file names (path traversal), repeated failed uploads, or unauthorised access. To prevent this, you should keep a few basic things in mind:

  • Log security-relevant events: Log all critical actions, such as file uploads, file accesses, path checks, or rejected requests, along with timestamps and context.
  • Record errors and exceptions: Make sure that you display a message for all errors and exceptions and record the exact cause in the log. This allows you to search for specific sources of errors later.
  • Use a central logging solution: Use proven logging frameworks like SLF4J in combination with Logback or Log4j2. This ensures that all log messages are recorded consistently, structured, and configurable for your environment.

Below is a customised version of the application, which allows you to insert log messages at security-critical points. This gives you essential insights during operation and will enable you to react quickly to problems.

The revised implementation uses the SLF4J (Simple Logging Facade for Java) logging API in combination with a specific implementation such as Logback. This separation of API and logging engine offers flexibility in choosing the underlying technology and facilitates integration into different runtime environments. The goal is to systematically record security-relevant events to ensure both transparency and traceability of security-critical actions during operation.

In the context of security-conscious web applications—especially those that work with user-generated content such as file uploads—comprehensive and structured logging is essential. You should explicitly record the following event types:

  • Path traversal tests (CWE-22): If a user attempts to access paths outside the intended memory area through manipulated input, you should report this with a WARN or higher log level. The logged information should include both the compromised path and the associated session or IP address to enable later correlation with other events.
  • Successful file uploads: Every completed upload process should be documented in the log, including the cleaned-up file name, the target path in the file system, and optional context information such as the user ID or upload time. This allows for complete traceability and also serves as an audit trail.
  • Upload error: If an exception occurs while saving the file (e.g., due to file system errors, access violations, or corrupted streams), you should log the full stack trace and all relevant metadata (file name, user context, time). This is essential for efficient error diagnosis and allows you to distinguish potential attack attempts from legitimate error cases.
  • Dynamic update of the file view: Logging can also be helpful for internal system actions, such as refreshing the list of available files to track when a file becomes visible or whether problems occurred during processing (e.g., access errors in locked directories).

Implementing these measures consistently allows you to detect and evaluate security-relevant incidents during ongoing operations promptly. Furthermore, a transparent logging strategy ensures that administrators and incident response teams have a solid database to rely on during an investigation. This strengthens your application’s ability to respond to attacks and fulfils key requirements for traceability, auditing, and compliance in security-critical IT systems.

Summary

We developed a functional file management application in Vaadin Flow in just a few steps. Users can upload files securely stored in the server directory and download them again. We used Java NIO to make file handling more efficient and secure. Additionally, we implemented safeguards against CWE-22 (Path Traversal), CWE-377 (Insecure Temporary File), and CWE-778 (Insufficient Logging) to ensure that attackers cannot gain unauthorised access to the file system, that temporary files are created securely, and that security-relevant events are comprehensively logged. This example demonstrates how easy it is to create a powerful user interface and implement server-side logic in Java with Vaadin Flow.

The next steps could be to extend the application, for example, by adding user permissions, customising the file overview, or integrating a search function for uploaded files. Vaadin offers numerous visual components to further improve the usability of your applications, but it is up to the developer to secure these features. Furthermore, we could extend the application with features such as drag-and-drop for file uploads, user role management, or a connection to a database for indexing files and storing metadata. However, with each additional feature, there are also additional attack vectors. It’s always important to find the right balance.

By enhancing security measures such as implementing robust file validation, sufficient logging mechanisms, and using Java NIO, we ensure that the application remains secure and efficient for both the developer and end users.

We’ll explore different aspects in the following parts, so it remains exciting.

Happy Coding

Sven


Discover more from Sven Ruppert

Subscribe to get the latest posts sent to your email.

Leave a Reply