File uploads are a common feature in ASP.NET Core applications. Users upload profile pictures, PDF documents, invoices, reports, and other files. However, accepting files without proper validation can expose an application to security risks.
One common mistake is checking only the file extension or the Content-Type header. Both values can be controlled by the client and may not accurately describe the uploaded file.
A safer approach is to check the file signature, also known as the magic bytes. These bytes identify the file format and provide an additional layer of validation before the application accepts an upload.
In this article, we will learn how to validate file signatures in ASP.NET Core, implement a secure upload endpoint, understand common mistakes, and apply practical security measures suitable for production applications.
Why File Extension Validation Is Not Enough
Consider an ASP.NET Core application that accepts image uploads. A developer might allow only files ending in .jpg, .png, or .pdf.
var extension = Path.GetExtension(file.FileName);
if (extension != ".jpg" &&
extension != ".png" &&
extension != ".pdf")
{
return BadRequest("Unsupported file type.");
}
This code checks the filename, but it does not verify the actual file content.
An attacker could rename an executable file to document.pdf and attempt to upload it. The extension check would accept the filename even though the content is not a valid PDF.
The Content-Type header has a similar limitation. A client can send image/jpeg even when the uploaded content is something else.
For this reason, file extension and MIME type validation should be treated as supporting checks rather than proof that a file is safe.
What Is a File Signature?
A file signature is a sequence of bytes located at a defined position in a file, commonly at the beginning. Applications can compare these bytes against known signatures to identify supported formats.
Here are some common examples.
File format | Extension | Example signature |
|---|
PNG | .png
| 89 50 4E 47 0D 0A 1A 0A
|
JPEG | .jpg, .jpeg
| FF D8 FF
|
PDF | .pdf
| 25 50 44 46
|
For example, the hexadecimal bytes 25 50 44 46 represent the characters %PDF.
Signature validation helps identify files that do not match their claimed format. However, it does not prove that the entire file is valid or harmless. A malicious file can contain a valid signature, and some formats support multiple valid structures.
A secure upload process should therefore combine signature checking with other validation and storage controls.
Step 1: Create an ASP.NET Core Upload Endpoint
Start with a controller that accepts an uploaded file using IFormFile.
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/files")]
public class FilesController : ControllerBase
{
[HttpPost("upload")]
[RequestSizeLimit(5 * 1024 * 1024)]
public async Task<IActionResult> Upload(
IFormFile file,
CancellationToken cancellationToken)
{
if (file == null || file.Length == 0)
{
return BadRequest("Please select a file.");
}
return Ok(new
{
Message = "File received.",
FileName = file.FileName
});
}
}
This endpoint accepts a file and rejects empty uploads. The request-size attribute sets a 5 MB limit for the action, but hosting and multipart form limits must also be configured consistently.
The example is only a starting point. It does not yet validate the file type or save the file securely.
In production, also consider authentication, authorization, request-size limits at the hosting layer, and rate limiting to prevent upload abuse.
Step 2: Validate File Signatures in C#
Instead of scattering signature checks throughout a controller, create a reusable validation service.
The following example supports PNG, JPEG, and PDF files. It checks the extension against the expected format and compares the file's opening bytes with the corresponding signature.
using Microsoft.AspNetCore.Http;
using System.IO;
public static class FileSignatureValidator
{
private sealed record FileType(
string Extension,
byte[] Signature);
private static readonly FileType[] SupportedTypes =
{
new(".png", new byte[]
{
0x89, 0x50, 0x4E, 0x47,
0x0D, 0x0A, 0x1A, 0x0A
}),
new(".jpg", new byte[]
{
0xFF, 0xD8, 0xFF
}),
new(".jpeg", new byte[]
{
0xFF, 0xD8, 0xFF
}),
new(".pdf", new byte[]
{
0x25, 0x50, 0x44, 0x46
})
};
public static async Task<bool> IsValidAsync(
IFormFile file,
CancellationToken cancellationToken = default)
{
if (file == null || file.Length == 0)
return false;
string extension =
Path.GetExtension(file.FileName);
var matchingType = SupportedTypes.FirstOrDefault(
type => string.Equals(
type.Extension,
extension,
StringComparison.OrdinalIgnoreCase));
if (matchingType == null ||
file.Length < matchingType.Signature.Length)
{
return false;
}
await using var stream = file.OpenReadStream();
byte[] header =
new byte[matchingType.Signature.Length];
int totalRead = 0;
while (totalRead < header.Length)
{
int bytesRead = await stream.ReadAsync(
header.AsMemory(totalRead),
cancellationToken);
if (bytesRead == 0)
return false;
totalRead += bytesRead;
}
return header.SequenceEqual(
matchingType.Signature);
}
}
The validator performs several important checks:
It rejects empty files.
It extracts the extension and checks it against an allowlist.
It rejects files that are too short to contain the expected signature.
It reads only the required number of opening bytes.
It compares the bytes with the signature associated with the extension.
The loop ensures the code handles streams that return fewer bytes than requested in a single read. The cancellation token also allows the read operation to be cancelled.
The example requires the System.Linq namespace for FirstOrDefault and SequenceEqual.
This validator deliberately supports only a small set of formats. Add other formats only when the application genuinely needs them and their validation requirements are understood.
Step 3: Save Files Using a Safer Approach
After validation, the application should save the file without trusting its original filename.
The following controller example integrates the validator and stores accepted files in a directory outside the web root.
[HttpPost("upload")]
[RequestSizeLimit(5 * 1024 * 1024)]
public async Task<IActionResult> Upload(
IFormFile file,
CancellationToken cancellationToken)
{
if (file == null || file.Length == 0)
{
return BadRequest("Please select a file.");
}
if (file.Length > 5 * 1024 * 1024)
{
return BadRequest("The file exceeds the 5 MB limit.");
}
bool valid = await FileSignatureValidator.IsValidAsync(
file,
cancellationToken);
if (!valid)
{
return BadRequest("The file type is not supported.");
}
string uploadDirectory = Path.Combine(
Path.GetTempPath(),
"MyApplicationUploads");
Directory.CreateDirectory(uploadDirectory);
string extension =
Path.GetExtension(file.FileName).ToLowerInvariant();
string storedFileName =
$"{Guid.NewGuid():N}{extension}";
string filePath = Path.Combine(
uploadDirectory,
storedFileName);
await using var output = new FileStream(
filePath,
FileMode.CreateNew,
FileAccess.Write,
FileShare.None,
bufferSize: 81920,
useAsync: true);
await file.CopyToAsync(output, cancellationToken);
return Ok(new
{
Message = "File uploaded successfully.",
FileId = storedFileName
});
}
The generated filename prevents the client from choosing the stored path. FileMode.CreateNew also avoids silently overwriting an existing file with the same generated name.
The temporary directory is an example location, not a complete production storage design. Configure a dedicated storage directory or private object-storage bucket with appropriate permissions, persistence, retention, and backup policies.
The application should store metadata in a database when necessary and expose files through authorized download endpoints rather than returning physical storage paths.
For stronger protection, validate the file again after copying if the storage pipeline or processing workflow requires it, and scan the file before making it available to users.
File Signature Validation vs. Other Validation Methods
Different validation techniques solve different problems.
Validation method | What it checks | Main limitation |
|---|
File extension | Claimed filename format | Filename can be changed |
MIME type | Declared content type | Client can manipulate the header |
File signature | Expected opening bytes | Does not prove the entire file is valid |
Format parser | Structure of the file | Parser vulnerabilities and resource exhaustion remain possible |
Malware scanning | Known or detectable malicious content | Cannot guarantee that every threat will be detected |
For example, a PDF can have the correct opening signature but still contain malformed structures or dangerous embedded content. A signature check should not replace a trusted parser, antivirus scanner, or content-disarm-and-reconstruction process when the application's risk profile requires one.
Best Practices for Secure File Uploads
1. Use an Extension Allowlist
Accept only the formats your application needs. Avoid accepting arbitrary extensions and attempting to block suspicious ones individually.
Compare extensions without case sensitivity because .PDF and .pdf should normally be treated consistently.
2. Apply Multiple Size Limits
Set a maximum file size in the application and configure the web server or reverse proxy to enforce a compatible request-size limit.
Remember that multipart requests contain additional data beyond the file itself. A request-size limit is not always identical to the permitted file size.
3. Store Uploads Outside the Web Root
Files stored in a publicly served directory may become directly accessible or executable depending on the hosting configuration.
Use private storage, restrictive permissions, and an authorized download mechanism. Keep uploaded files separate from application binaries and configuration files.
4. Generate Server-Side File Names
Never use the original filename directly as a filesystem path. Generate a unique identifier and preserve only a server-approved extension when one is needed.
Keep the original filename separately as metadata if the application needs to display it to the user.
5. Scan and Parse Files Safely
For high-risk applications, scan uploaded files for malware and use appropriate format parsers. Process complex files in isolated environments when practical.
Image libraries, document parsers, and archive extractors can themselves be exposed to malformed input. Apply resource limits and keep dependencies updated.
6. Enforce Authorization
Authentication alone does not determine whether a user can upload a file to a particular account or access an uploaded document.
Check ownership and permissions during both upload and download operations. Do not rely on unpredictable file IDs as a substitute for authorization.
7. Log Security-Relevant Events
Record rejected uploads, validation failures, scanning results, and unusual upload patterns. Avoid logging file contents, credentials, or sensitive personal information.
Use monitoring to identify repeated failures and potential abuse without exposing unnecessary details in API error messages.
Common Mistakes to Avoid
Trusting the extension: A filename does not establish the real file format.
Trusting the MIME type: The client controls the submitted content-type header.
Treating a signature match as proof of safety: Signature validation is an identification check, not a complete malware inspection.
Saving files under their original names: This can introduce path manipulation and overwrite risks.
Allowing unlimited uploads: Large files and repeated requests can exhaust application resources.
Publishing files immediately: Scan or process files before making them available when required by the application's security policy.
Ignoring storage permissions: A correctly validated file can still become a security problem if the storage location is exposed.
Troubleshooting File Upload Validation
Valid files are rejected
Check whether the extension matches the configured allowlist and whether the signature is correct for the expected format. Some formats have multiple valid structures, so a simple signature comparison may not cover every legitimate variant.
Large uploads fail before reaching the controller
Inspect the request-size limits configured in ASP.NET Core, the reverse proxy, and the hosting environment. The smallest applicable limit may reject the request first.
Uploads succeed but downloads fail
Verify that the file was written successfully, that the storage directory is persistent, and that the download endpoint checks authorization and returns an appropriate content type.
A file passes validation but causes problems during processing
A matching signature does not guarantee that the file is structurally valid. Use a format-aware parser, handle parsing exceptions, and apply resource limits to prevent excessive memory or CPU consumption.
Advantages and Disadvantages of File Signature Validation
Advantages
Detects many files whose contents do not match their claimed extensions.
Adds a useful security layer with relatively little code for simple formats.
Helps reject unsupported formats before storage or further processing.
Can be implemented as a reusable validation component.
Disadvantages
Requires maintaining an allowlist of supported formats and signatures.
Does not detect every malicious file or validate the complete file structure.
Some formats have complex or variable signatures.
Requires additional validation and scanning for applications handling sensitive or high-risk documents.
Summary
Checking file signatures is an important step toward securing file uploads in ASP.NET Core. Unlike extension and MIME type checks, it examines the actual opening bytes of an uploaded file to determine whether they match an expected format.
However, signature validation should be one part of a broader upload security strategy. Combine it with extension allowlists, size limits, server-generated filenames, private storage, authorization, and malware scanning or format-aware parsing where appropriate.
The goal is not simply to accept files that look correct. It is to ensure that uploaded content is validated, stored safely, and made available only through controlled application workflows.
Jasen FiciPosted Oct 28, 2026, 12:48 PM
This made today's DotNetNews: https://dotnetnews.beehiiv.com/p/new-post-c396