Vaadin Flow 24.3 introduces quality-of-life improvements, simplified component styling, and more.
Blog

Secure coding practices 2: Binary upload validation

By  
Sami Ekblad
Sami Ekblad
·
On Dec 19, 2023 3:49:07 PM
·

When it comes to web applications, security is a paramount concern, and the Open Web Application Security Project (OWASP) offers essential guidelines to fortify your app against various vulnerabilities. 

In the previous article of this series, we looked at the basic user input validation. In this article, we'll focus on binary data input validation using Java and Vaadin. You can find more information, plain Java examples, and tutorials on secure coding practices at github.com/secure-coding-practices.

Validating file size

OWASP recommends validating the uploaded file size to prevent Denial of Service (DoS) attacks. In Vaadin, this can be seamlessly integrated with the Upload component:

upload.setAcceptedFileCount(1);
upload.setMaxFileSize(10 * 1024 * 1024);

In this snippet, we restrict the upload to a single file and set a maximum file size of 10 MB with only JPEG Images. These are passed to the browser to instruct and guide the user in selecting the right kind of files to upload. However, one must be aware that they don’t prevent malicious requests from being made without a browser, so the main validation should happen on the server. 

Here is an example of doing the size validation when the upload is underway and interrupting uploading when necessary:

upload.addProgressListener(e -> {
    if (e.getContentLength() > MAX_FILE_SIZE_BYTES 
|| e.getReadBytes() > MAX_FILE_SIZE_BYTES) {
        imageUpload.interruptUpload();
        Notification.show("Content too long");
    }
});

Verifying file content 

It's not just the size that matters but also the type of file. Ensuring the uploaded file matches the expected content type mitigates the risk of malicious file uploads. One way to achieve this is by limiting the user to upload only specific file types, for example, JPEG files. You can achieve this by setting the  upload to accept MIME Type “image/jpeg.” 

upload.setAcceptedFileTypes("image/jpeg");

However, this only prompts browsers to allow users to select files with certain file extensions. What if a file with the JPEG file extension was not a JPEG at all? While storing the data might not be a problem, it could lead to problems elsewhere in the system later on.

Here is an example of how to check a JPEG file’s ‘Start Of Image’ file header once the upload has been completed. You can do this before permanently saving the files:

Upload upload = new Upload(imageBuffer);
upload.addSucceededListener(event -> {
    try {
        byte[] data = uploadBuffer.getInputStream().readAllBytes();
        if (data == null || data.length < 2
                || (data[0] & 0xff) != 0xff
                || (data[1] & 0xff) != 0xd8) {
            Notification.show("Upload is not valid JPEG file.");
            return;
        }
    } catch (Exception ex) { return; }
    /* All ok. Process and store JPEG */
});

Here, we check if the uploaded file is a JPEG. If not, appropriate actions can be taken, such as rejecting the file or notifying the user. 

Ensuring integrity with checksum validation

By integrating SHA256 validation, you can enhance security and trust, using simple UI components and backend logic to compare the file's checksum against a provided or calculated value. Here, you can use a separate TextField to allow users to provide a SHA-256 and re-validate that on the server side. 

TextField checksumField = new TextField("SHA-256 Checksum");
MemoryBuffer uploadBuffer = new MemoryBuffer();
Upload uploadField = new Upload(uploadBuffer);
add(checksumField, uploadField);

uploadField.addSucceededListener(event -> {
    boolean validationOk = false;
    try {
        byte[] data = uploadBuffer.getInputStream().readAllBytes();
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        validationOk = HexFormat.of().formatHex(md.digest(data)).equals(checksum.getValue());
    } catch (Exception ex) { validationOk = false; }

    // Discard the upload and give user feedback
    if (!validationOk) {
        Notification.show("Upload checksum validation failed.");
        return;
    }
});

Securely handling file uploads

By implementing these methods, you ensure that your Vaadin application complies with OWASP guidelines and provides a robust defense against file upload-related security threats. Also, using a centralized input validation routine for the application and well-known libraries and creating a set of application-specific validation helpers makes sense.

You can find these examples, along with additional validation methods, at https://github.com/secure-coding-practices

Sami Ekblad
Sami Ekblad
Sami Ekblad is one of the original members of the Vaadin team. As a DX lead he is now working as a developer advocate, to help people the most out of Vaadin tools. You can find many add-ons and code samples to help you get started with Vaadin. Follow at – @samiekblad
Other posts by Sami Ekblad