ASP.NET Core  

Secure File Upload Handling in ASP.NET Core MVC

Introduction

File uploads are a common requirement in modern web applications. From uploading a profile picture to submitting documents, users frequently need this feature.

But here’s the catch. Improper file upload handling can become a huge security risk. Attackers may upload malicious files, attempt path traversal, or even perform denial-of-service attacks.

In this article, we’ll explore secure file upload handling in ASP.NET Core MVC with best practices, sample code, and step-by-step guidance.

Why Is File Upload Security Important?

Insecure file uploads can lead to:

  • Malware Upload—Hackers upload .exe,.js files disguised as images.

  • Path Traversal – Using filenames like ../../web.config to overwrite sensitive files.

  • Denial of Service (DoS)—Uploading very large files to exhaust server resources.

  • MIME Type Spoofing – Uploading dangerous files renamed as .jpg or .png.

  • Unauthorized Access – Files stored in wwwroot are directly accessible via URL.

Therefore, a multi-layered defense is critical.

Best Practices for Secure File Uploads

Here are the golden rules for handling file uploads in ASP.NET Core MVC:

  1. Limit File Size – Prevent oversized uploads.

  2. Restrict File Types—Allow only specific file extensions.

  3. Use Safe Filenames—Never trust user-supplied filenames.

  4. Store Outside wwwroot – Prevent direct public access.

  5. Validate File Content—Don’t rely solely on extensions.

  6. Scan Files (Optional)—Use antivirus or third-party scanning APIs.

  7. Use HTTPS—Ensure secure transport during upload.

Step-by-Step Implementation in ASP.NET Core MVC

Let’s implement secure file upload handling.

Step 1: Configure Maximum File Size

In Program.cs, set the maximum upload size:

using Microsoft.AspNetCore.Http.Features;

var builder = WebApplication.CreateBuilder(args);

builder.Services.Configure<FormOptions>(options =>
{
    options.MultipartBodyLengthLimit = 10 * 1024 * 1024; // 10 MB limit
});

var app = builder.Build();
app.Run();

Step 2: Create the Upload Model

using System.ComponentModel.DataAnnotations;

public class FileUploadViewModel
{
    [Required]
    public IFormFile File { get; set; }
}

Step 3: Create the Controller

using Microsoft.AspNetCore.Mvc;

public class FileUploadController : Controller
{
    private readonly long _fileSizeLimit = 10 * 1024 * 1024; // 10 MB
    private readonly string[] _permittedExtensions = { ".jpg", ".png", ".pdf" };
    private readonly string _targetFilePath;

    public FileUploadController(IWebHostEnvironment env)
    {
        // Store files securely outside wwwroot
        _targetFilePath = Path.Combine(env.ContentRootPath, "SecureUploads");
        if (!Directory.Exists(_targetFilePath))
        {
            Directory.CreateDirectory(_targetFilePath);
        }
    }

    [HttpGet]
    public IActionResult Index() => View();

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Upload(FileUploadViewModel model)
    {
        if (model.File == null || model.File.Length == 0)
        {
            ModelState.AddModelError("File", "Please select a file.");
            return View("Index");
        }

        //  Validate file size
        if (model.File.Length > _fileSizeLimit)
        {
            ModelState.AddModelError("File", "File size exceeds the limit.");
            return View("Index");
        }

        // Validate extension
        var ext = Path.GetExtension(model.File.FileName).ToLowerInvariant();
        if (string.IsNullOrEmpty(ext) || !_permittedExtensions.Contains(ext))
        {
            ModelState.AddModelError("File", "Invalid file type.");
            return View("Index");
        }

        // Generate safe filename
        var trustedFileName = Path.GetRandomFileName() + ext;
        var filePath = Path.Combine(_targetFilePath, trustedFileName);

        // Save securely
        using (var stream = new FileStream(filePath, FileMode.Create))
        {
            await model.File.CopyToAsync(stream);
        }

        ViewBag.Message = "File uploaded successfully!";
        return View("Index");
    }
}

Step 4: Create the Razor View

@model FileUploadViewModel

<h2>Secure File Upload in ASP.NET Core MVC</h2>

<form asp-action="Upload" method="post" enctype="multipart/form-data">
    <div>
        <input type="file" asp-for="File" />
        <span asp-validation-for="File" class="text-danger"></span>
    </div>
    <button type="submit">Upload</button>
</form>

@if (ViewBag.Message != null)
{
    <div class="alert alert-success">@ViewBag.Message</div>
}

Additional Security Enhancements

  • Validate File Signature (Magic Numbers)
    Instead of trusting extensions, check file headers.

  • Quarantine Uploads
    Save files temporarily before scanning.

  • Logging & Monitoring
    Log all uploads for auditing.

  • Rate Limiting
    Prevent abuse from excessive uploads.

Conclusion

Secure file upload handling in ASP.NET Core MVC requires layered security:

  • Restrict file size and types

  • Validate extensions & MIME type

  • Store outside. wwwroot

  • Use safe filenames

By following these practices, you can build robust and secure upload functionality in your ASP.NET Core MVC applications.