Use ExceptionFilter to hide errors in your WebApi
One of the more common security mistakes is exposing too much information to our users. In the beginning of ASP.NET we had the famous "yellow screen of death."
This screen was helpful to the developers trying to debug the program, it contained the error message, the stack trace and other information. Having helpful error messages, such as what SQL statement the code attempted to run, is very helpful in tracking down a bug. It is also helpful to an attacker because they can figure out your database schema very quickly using tools such as SQLMap.
Thankfully most people wised up and stopped showing the "yellow screen of death."
Most developers using the ASP.NET stack are now using ASP.NET MVC and ASP.NET WebApi. The ASP.NET MVC site is a host for a Single Page Application (SPA) with a lot of interaction with the WebApi. Now errors can occur at anytime during the page's life cycle. Again, we don't want to expose error messages back to the UI. Any user using the web browsers developer tools can use the network trace and see the error messages being returned.
The message should be generic and any error should be logged.
Exception handling should be done at a global level. Like my previous post stated, expecting developers to write specific code all the time is just asking for trouble. And sprinkling Try/Catch or Try/Catch/Finally blocks throughout the code makes the code harder to read.
The solution?
More filters! Yeah!
Thankfully, the WebApi framework has a handy dandy filter type built in called ExceptionFilterAttribute. It is very easy to implement. All errors are logged but we only show a generic exception to our users.
public sealed class ExceptionHandleFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
if (actionExecutedContext.Response != null)
{
return;
}
if (actionExecutedContext.Exception == null)
{
return;
}
var logger = new LoggerApi();
if (actionExecutedContext.Exception is UserDisplayException)
{
logger.LogWarning(actionExecutedContext.Exception.Message);
actionExecutedContext.Response = actionExecutedContext.Request.CreateErrorResponse(HttpStatusCode.BadGateway, actionExecutedContext.Exception.Message);
}
else if (actionExecutedContext.Exception is TaskCanceledException == false && actionExecutedContext.Exception is OperationCanceledException == false)
{
logger.LogException(actionExecutedContext.Exception);
actionExecutedContext.Response = actionExecutedContext.Request.CreateErrorResponse(HttpStatusCode.InternalServerError, "We're Sorry. An unexpected error has occurred. If this continues please contact Tech Support.");
}
}
}
You will notice we look at the exception type and if it is UserDisplayException then show the actual exception message. Otherwise show the generic error message. There are very rare times when in the code we want to throw an exception and show a friendly error message to the user.
The exception filter sitting at the global level. It has no idea why certain code failed, such as a call to another RESTful endpoint, or a SQL Exception. In the event a service call fails there might be a need to let the user know which service failed. This is why a special exception is created to handle this rare case.
using System;
namespace MySite.Model
{
[Serializable]
public class UserDisplayException : Exception
{
public UserDisplayException(string message)
: base(message)
{
}
public UserDisplayException(string message, Exception inner)
: base(message, inner)
{
}
}
}
Now we can use that when it is absolutely needed.
if (!results.IsValid)
{
throw new UserDisplayException("This is a special error message. Please Contact Tech Support");
}
You will also notice we exclude any errors related to Task being canceled or operation being canceled. This is a quirk in WebApi which will throw an error message if the user cancels the request in the middle of processing it. We don’t care about those so we just exclude them.
There you have it, a very simple way to hide exception messages in your WebApi in a global level.