DEV Community

Cover image for Defense In-Depth: Designing an HTTP Content Length Restriction Middleware - ASP.NET 5 (or .NET Core)
Satish Yadav
Satish Yadav

Posted on • Updated on • Originally published at blog.satishyadav.com

Defense In-Depth: Designing an HTTP Content Length Restriction Middleware - ASP.NET 5 (or .NET Core)

The What?

We want to design a Middleware that - when plugged into an ASP.NET 5 (or .NET Core) application pipeline - restricts the input payload size, so attacks which rely of sending a bigger sized (or larger than our specified size) payload can be rejected by Application itself.

The Why?

You'd argue that I have a firewall and other Security and Network devices so why do I need to implement this at Application level?

Well, if you follow Defence-in-Depth, US-CERT (United States Computer Emergency Readiness Team) has this to say about redundant security mechanisms:

Layering security defenses in an application can reduce the chance of a successful attack. Incorporating redundant security mechanisms requires an attacker to circumvent each mechanism to gain access to a digital asset. For example, a software system with authentication checks may prevent an attacker that has subverted a firewall. Defending an application with multiple layers can prevent a single point of failure that compromises the security of the application.

The How? - Let's code

So I broken down this designing to 3 parts:

  1. Create the basic logic
  2. Make it a re-useable Middleware
  3. Make it look like a Native Middleware

So let's get into it:

1. Create the basic logic

We want to check the ContentLength of Request payload against our limit, if it exceeds, it should send the HTTP 413 Entity Too Large as per IETF RFC 7231 specification of HTTP.

if (httpContext.Request.ContentLength > SOME_LIMIT)
{    
    httpContext.Response.StatusCode = StatusCodes.Status413RequestEntityTooLarge;
    await httpContext.Response.WriteAsJsonAsync(new
    {
        Title = "Request too large",
        Status = StatusCodes.Status413RequestEntityTooLarge,
        Type = "https://tools.ietf.org/html/rfc7231#section-6.5.11",
    });
    await httpContext.Response.CompleteAsync();
}
else
{
    await _requestDelegate.Invoke(httpContext);
}
```
{% endraw %}

Let's break it down:

* First we're checking the content length.
* If it's greater than our limit, we're writing a {% raw %}`JSON`{% endraw %} response with {% raw %}`HTTP 413`{% endraw %}. And completing the response as we don't need to execute any further Middleware.
* If not, we can continue the Middleware pipe and execute next Middleware.

### 2. Create a re-usable middleware
Now we have our basic logic ready, let's create a re-usable middleware out of it.
We need to do following:
1. Create a Middleware class and run our logic in {% raw %}`Invoke`{% endraw %} or {% raw %}`InvokeAsync`{% endraw %} method.
2. Take input at the runtime instead of hard-coding it.
3. Add logging

Let's complete this one by one:

#### 2.1.  Create a Middleware class and run our logic in {% raw %}`Invoke`{% endraw %} or {% raw %}`InvokeAsync`{% endraw %} method.
Here's a basic structure of a Middleware class looks like. It should have an {% raw %}`Invoke`{% endraw %} or {% raw %}`InvokeAsync`{% endraw %} method which would be called by runtime based on our configuration.
{% raw %}


````csharp
public class ContentLengthRestrictionMiddleware
{
    private readonly RequestDelegate _requestDelegate;

    public ContentLengthRestrictionMiddleware(RequestDelegate nextRequestDelegate)
    {
        _requestDelegate = nextRequestDelegate;        
    }
    public async Task InvokeAsync(HttpContext httpContext)
    {
        if (httpContext.Request.ContentLength > SOME_LIMIT)
        {
            httpContext.Response.StatusCode = StatusCodes.Status413RequestEntityTooLarge;
            await httpContext.Response.WriteAsJsonAsync(new
            {
                Title = "Request too large",
                Status = StatusCodes.Status413RequestEntityTooLarge,
                Type = "https://tools.ietf.org/html/rfc7231#section-6.5.11",
            });
            await httpContext.Response.CompleteAsync();
        }
        else
        {
            await _requestDelegate.Invoke(httpContext);
        }
    }
}                  
```
{% endraw %}

#### 2.2. Take input at the runtime instead of hard-coding it.
Currently we're hard-coding the limit ({% raw %}`SOME_LIMIT`{% endraw %}), we can create a class and take input at runtime.
{% raw %}

````csharp
public class ContentLengthRestrictionOptions
{
    public long ContentLengthLimit { get; set; }
}
```
{% endraw %}

And modify our Middleware to use this:
{% raw %}


````csharp
public class ContentLengthRestrictionMiddleware
{
    private readonly ContentLengthRestrictionOptions _contentLengthRestrictionOptions;
    private readonly RequestDelegate _requestDelegate;

    public ContentLengthRestrictionMiddleware(RequestDelegate nextRequestDelegate, ContentLengthRestrictionOptions contentLengthRestrictionOptions)
    {
        _requestDelegate = nextRequestDelegate;
        _contentLengthRestrictionOptions = contentLengthRestrictionOptions;        
    }
    public async Task InvokeAsync(HttpContext httpContext)
    {
        if (_contentLengthRestrictionOptions != null && _contentLengthRestrictionOptions.ContentLengthLimit > 0 && httpContext.Request.ContentLength > _contentLengthRestrictionOptions.ContentLengthLimit)
        {
       httpContext.Response.StatusCode = StatusCodes.Status413RequestEntityTooLarge;
            await httpContext.Response.WriteAsJsonAsync(new
            {
                Title = "Request too large",
                Status = StatusCodes.Status413RequestEntityTooLarge,
                Type = "https://tools.ietf.org/html/rfc7231#section-6.5.11",
            });
            await httpContext.Response.CompleteAsync();
        }
        else
        {
            await _requestDelegate.Invoke(httpContext);
        }
    }
}
```
{% endraw %}


#### 2.3. Add logging

Here's little tricky part, you can't just inject an {% raw %}`ILogger<T>`{% endraw %} and expect runtime to give you that dependency, you can get an {% raw %}`ILoggerFactory`{% endraw %} and then you can create your own {% raw %}`ILogger<T>`{% endraw %}.
{% raw %}


````csharp
public class ContentLengthRestrictionMiddleware
{
    private readonly ContentLengthRestrictionOptions _contentLengthRestrictionOptions;
    private readonly ILogger<ContentLengthRestrictionMiddleware> _logger;
    private readonly RequestDelegate _requestDelegate;

    public ContentLengthRestrictionMiddleware(RequestDelegate nextRequestDelegate, ContentLengthRestrictionOptions contentLengthRestrictionOptions, ILoggerFactory loggerFactory)
    {
        _requestDelegate = nextRequestDelegate;
        _contentLengthRestrictionOptions = contentLengthRestrictionOptions;
        _logger = loggerFactory.CreateLogger<ContentLengthRestrictionMiddleware>();
    }
    public async Task InvokeAsync(HttpContext httpContext)
    {
        if (_contentLengthRestrictionOptions != null && _contentLengthRestrictionOptions.ContentLengthLimit > 0 && httpContext.Request.ContentLength > _contentLengthRestrictionOptions.ContentLengthLimit)
        {
            _logger.LogWarning("Rejecting request with Content-Length {0} more than allowed {1}.", httpContext.Request.ContentLength, _contentLengthRestrictionOptions.ContentLengthLimit);
            httpContext.Response.StatusCode = StatusCodes.Status413RequestEntityTooLarge;
            await httpContext.Response.WriteAsJsonAsync(new
            {
                Title = "Request too large",
                Status = StatusCodes.Status413RequestEntityTooLarge,
                Type = "https://tools.ietf.org/html/rfc7231#section-6.5.11",
            });
            await httpContext.Response.CompleteAsync();
        }
        else
        {
            await _requestDelegate.Invoke(httpContext);
        }
    }
}
```
{% endraw %}

I've added a log only when the Middleware rejects the requests.

### 3. Make it look like a native Middleware.
We can create an Extension method on {% raw %}`IApplicationBuilder`{% endraw %}, something to call like {% raw %}`UseXXX`{% endraw %}, so it feels like a Native Middleware.
{% raw %}


````csharp
public static class MiddlewareExtensions
{
    public static IApplicationBuilder UseContentLengthRestriction(this IApplicationBuilder builder, ContentLengthRestrictionOptions contentLengthRestrictionOptions)
        => builder.UseMiddleware<ContentLengthRestrictionMiddleware>(contentLengthRestrictionOptions);
}
```
{% endraw %}

And we can use it like:
{% raw %}


````csharp
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    //This is our Middleware 😍
    app.UseContentLengthRestriction(new ContentLengthRestrictionOptions
    {
        ContentLengthLimit = 10
    });

    /// Other configuration
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}
```
{% endraw %}


## 4. The Execution
Sending a {% raw %}`POST`{% endraw %} request to our endpoint:
{% raw %}

````curl
curl -X POST "https://localhost:5001/WeatherForecast" -H  "accept: */*" -H  "Content-Type: application/json" -d "{\"date\":\"2021-08-22T14:17:58.115Z\",\"temperatureC\":0,\"summary\":\"string\"}"
```
{% endraw %}


Results in following:
{% raw %}

````json
{
  "title": "Request too large",
  "status": 413,
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.11"
}
```
{% endraw %}


And logs following in to stdout:
{% raw %}

Enter fullscreen mode Exit fullscreen mode

warn: AwesomeApi.ContentLengthRestrictionMiddleware[0]
Rejecting request with Content-Length 71 more than allowed 10.




## Conclusion
That's it! Congratulations! You just designed and created a custom Middleware! Give yourself a pat in the back. Don't forget to stretch your shoulders and neck once in a while.

Happy Coding!

## Code
Source Code is available at:
[https://github.com/iSatishYadav/ContentLengthRestrictionMiddleware](https://github.com/iSatishYadav/ContentLengthRestrictionMiddleware)

If you liked it, go show some love to the repo and star it. 

Originally Posted at my blog:
[https://blog.satishyadav.com/defense-in-depth-designing-an-http-content-length-restriction-middleware-asp-net-5-or-net-core](https://blog.satishyadav.com/defense-in-depth-designing-an-http-content-length-restriction-middleware-asp-net-5-or-net-core?utm_source=dt)
Enter fullscreen mode Exit fullscreen mode

Latest comments (0)