In the first post of this series, we explored how content type validation serves as the first line of defense against malicious file uploads. Today, we're tackling another critical security concern: file size validation and why it's essential for protecting your application from resource exhaustion attacks.
The threat: Death by a thousand uploads
File size validation might seem like a simple feature requirement, but it's actually a crucial security control. Without proper size limits, attackers can:
- Exhaust disk space: Fill up your storage with massive files, causing system failures
- Consume bandwidth: Drain network resources by uploading gigantic files repeatedly
- Trigger out-of-memory errors: Crash your application by forcing it to process files larger than available memory
- Enable denial-of-service attacks: Tie up server resources processing oversized files, preventing legitimate users from accessing your application
- Inflate storage costs: In cloud environments where storage is billed per gigabyte, unrestricted uploads can lead to unexpectedly high costs
A single malicious actor uploading even a few multi-gigabyte files can bring down an unprotected application or rack up substantial cloud storage bills.
Our implementation: Simple but effective
File size validation is the first step in our validation pipeline (before content type checking) because there's no point in performing expensive validation operations on files that are too large anyway.
Here's our implementation:
Design decisions explained
1. Configurable limits
The maximum file size is injected as a constructor parameter rather than being hardcoded. This allows different parts of your application to have different size limits based on use case. For example:
2. Early validation
File size is checked first in the pipeline because:
- It's a fast, cheap operation (just reading a property)
- It prevents wasting resources on oversized files that would fail anyway
- It stops potential attacks before expensive operations like virus scanning occur
Looking at our pipeline configuration from the previous post:
Why IFormFile.Length is safe to use
You might wonder if checking file.Length
is safe—couldn't an attacker manipulate this value? The good news is that IFormFile.Length
represents the actual uploaded content length as determined by ASP.NET Core, not a user-supplied header. The framework calculates this from the actual received data.
However, there's an important caveat: this validation only happens after the file has been uploaded to the server (either to memory or a temporary file). To truly prevent resource exhaustion, you need defense in depth.
Defense in depth: Multiple layers of protection
Our FileSizeValidationStep
works in conjunction with requests size limits in ASP.NET Core:
And at the IIS level (in our case):
What's next
File size validation protects against resource exhaustion, but it doesn't address the content of the files themselves. In the next post, we'll explore file signature validation—the technique that ensures files are actually what they claim to be, regardless of their extension or content type header.