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