ASP.NET Core Secure Public APIs
ASP.NET Core is a popular framework. Key benefits include features like cross-platform execution, high-performance, built-in
Dependency Injection and modular HTTP request pipeline.
The challenge
ASP.NET Core provides support for many authentication providers to secure the app through numerous authentication workflows. However, in many scenarios, we have to provide a web application/site that relies on unauthenticated APIs with anonymous access.
For example, we have a list of products in a database and we want to display these products on a web page. We may write an API to provide the list of products and have the front end (web site) retrieve this list via the API and display them on our public products web page.
Without applying a level of security, such architectures can be a security vulnerability open for exploitation.
Available Security Controls in ASP.NET
ASP.NET Core provides a solution for common vulnerabilities, including,
- Cross-Site Scripting
- SQL Injection,
- Cross-Site Request Forgery (CSRF)
- Open redirects
Going a step further
As a developer, we should also protect our apps from other common attack vectors, including,
- Distributed denial-of-service (DDOS)
- Denial-of-service (DOS)
- Bulk data egress
- Probe response
- Scraping
Two steps we can take are referer header checks and rate-limiting, discussed in detail below.
Use an IP Based Request Limit Action Filter
We can limit clients to a number of request within the specified time span to prevent malicious bot attacks. I have created an IP Based Request Limit Action Filter in ASP.NET Core. Do be aware that multiple clients may sit behind one IP address so you may want to cater for this in your limits, or combine the IP address with other request data to make requests more unique.
In order to yse the filter, you just need to add ActionAttribute on top of Controller Action.
- [HttpGet()]
- [ValidateReferrer]
- [RequestLimit("Test-Action", NoOfRequest = 5, Seconds = 10)]
- public async Task<ActionResult> GetAsync(CancellationToken ct)
- {
-
- }
Here is the implementation of the filter,
- namespace Security.Api.Filters {
- using System;
- using System.Net;
- using Microsoft.AspNetCore.Mvc;
- using Microsoft.AspNetCore.Mvc.Filters;
- using Microsoft.Extensions.Caching.Memory;
-
-
-
-
- [AttributeUsage(AttributeTargets.Method)]
- public class RequestLimitAttribute: ActionFilterAttribute {
- public RequestLimitAttribute(string name) {
- Name = name;
- }
- public string Name {
- get;
- }
- public int NoOfRequest {
- get;
- set;
- } = 1;
- public int Seconds {
- get;
- set;
- } = 1;
- private static MemoryCache Cache {
- get;
- } = new MemoryCache(new MemoryCacheOptions());
- public override void OnActionExecuting(ActionExecutingContext context) {
- var ipAddress = context.HttpContext.Request.HttpContext.Connection.RemoteIpAddress;
- var memoryCacheKey = $ "{Name}-{ipAddress}";
- Cache.TryGetValue(memoryCacheKey, out int prevReqCount);
- if (prevReqCount >= NoOfRequest) {
- context.Result = new ContentResult {
- Content = $ "Request limit is exceeded. Try again in {Seconds} seconds.",
- };
- context.HttpContext.Response.StatusCode = (int) HttpStatusCode.TooManyRequests;
- } else {
- var cacheEntryOptions = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(Seconds));
- Cache.Set(memoryCacheKey, (prevReqCount + 1), cacheEntryOptions);
- }
- }
- }
- }
Add Referer Check Action Filter
To protect APIs from abuse and to provide additional protection against Cross-Site Request Forgery (CSRF) attacks, a security check is performed on the request Referer header for every REST API request sent to the server.
This validates where the API request has come from. I have created a Referer Check Action Filter in ASP.NET Core. It also prevents access from tools like POSTMAN, REST Client etc.
You just need to add ActionAttribute on top of Controller Action.
- [HttpGet()]
- [ValidateReferrer]
- public async Task<ActionResult> GetAsync(CancellationToken ct)
- {
-
- }
Here is the implementation of the filter,
- namespace Security.Api.Filters {
- using Microsoft.AspNetCore.Http;
- using Microsoft.AspNetCore.Mvc;
- using Microsoft.AspNetCore.Mvc.Filters;
- using Microsoft.Extensions.Configuration;
- using System;
- using System.Linq;
- using System.Net;
-
-
-
-
- [AttributeUsage(AttributeTargets.Method)]
- public sealed class ValidateReferrerAttribute: ActionFilterAttribute {
- private IConfiguration _configuration;
-
-
-
- public ValidateReferrerAttribute() {}
-
-
-
-
- public override void OnActionExecuting(ActionExecutingContext context) {
- _configuration = (IConfiguration) context.HttpContext.RequestServices.GetService(typeof(IConfiguration));
- base.OnActionExecuting(context);
- if (!IsValidRequest(context.HttpContext.Request)) {
- context.Result = new ContentResult {
- Content = $ "Invalid referer header"
- };
- context.HttpContext.Response.StatusCode = (int) HttpStatusCode.ExpectationFailed;
- }
- }
-
-
-
-
-
-
-
- private bool IsValidRequest(HttpRequest request) {
- string referrerURL = "";
- if (request.Headers.ContainsKey("Referer")) {
- referrerURL = request.Headers["Referer"];
- }
- if (string.IsNullOrWhiteSpace(referrerURL)) return false;
-
- var allowedUrls = _configuration.GetSection("CorsOrigin").Get < string[] > ()?.Select(url => new Uri(url).Authority).ToList();
-
- var host = request.Host.Value;
- allowedUrls.Add(host);
- bool isValidClient = allowedUrls.Contains(new Uri(referrerURL).Authority);
- return isValidClient;
- }
- }
- }
Add DoS Attack Middleware
DoS attacks overwhelm your APIs, making them unresponsive and/or expensive if you have autoscale configured.. There are different ways to avoid this problem through request throttling. Here is one option using middleware to restrict the number of request from a particular client IP address.
Below is code for DosAttackMiddleware.cs
- namespace Security.Api.Middlewares {
- using Microsoft.AspNetCore.Http;
- using System.Collections.Generic;
- using System.Linq;
- using System.Net;
- using System.Threading.Tasks;
- using System.Timers;
- public sealed class DosAttackMiddleware {
- #region Private fields
- private static Dictionary < string, short > _IpAdresses = new Dictionary < string, short > ();
- private static Stack < string > _Banned = new Stack < string > ();
- private static Timer _Timer = CreateTimer();
- private static Timer _BannedTimer = CreateBanningTimer();
- #endregion
- private
- const int BANNED_REQUESTS = 10;
- private
- const int REDUCTION_INTERVAL = 1000;
- private
- const int RELEASE_INTERVAL = 5 * 60 * 1000;
- private RequestDelegate _next;
- public DosAttackMiddleware(RequestDelegate next) {
- _next = next;
- }
- public async Task InvokeAsync(HttpContext httpContext) {
- string ip = httpContext.Connection.RemoteIpAddress.ToString();
- if (_Banned.Contains(ip)) {
- httpContext.Response.StatusCode = (int) HttpStatusCode.Forbidden;
- }
- CheckIpAddress(ip);
- await _next(httpContext);
- }
-
-
-
-
- private static void CheckIpAddress(string ip) {
- if (!_IpAdresses.ContainsKey(ip)) {
- _IpAdresses[ip] = 1;
- } else if (_IpAdresses[ip] == BANNED_REQUESTS) {
- _Banned.Push(ip);
- _IpAdresses.Remove(ip);
- } else {
- _IpAdresses[ip]++;
- }
- }
- #region Timers
-
-
-
-
- private static Timer CreateTimer() {
- Timer timer = GetTimer(REDUCTION_INTERVAL);
- timer.Elapsed += new ElapsedEventHandler(TimerElapsed);
- return timer;
- }
-
-
-
-
-
- private static Timer CreateBanningTimer() {
- Timer timer = GetTimer(RELEASE_INTERVAL);
- timer.Elapsed += delegate {
- if (_Banned.Any()) _Banned.Pop();
- };
- return timer;
- }
-
-
-
-
- private static Timer GetTimer(int interval) {
- Timer timer = new Timer();
- timer.Interval = interval;
- timer.Start();
- return timer;
- }
-
-
-
- private static void TimerElapsed(object sender, ElapsedEventArgs e) {
- foreach(string key in _IpAdresses.Keys.ToList()) {
- _IpAdresses[key]--;
- if (_IpAdresses[key] == 0) _IpAdresses.Remove(key);
- }
- }
- #endregion
- }
- }
Conclusion
Unauthenticated APIs are open to abuse. We must prevent obvious attack vectors by adding additional code. Hopefully this blog makes these controls easy to implement whilst making attacker’s lives more difficult.