Decrypt Secrets Into ASP.NET Core MVC Action Arguments Using Action Filters
source link: https://khalidabuhakmeh.com/decrypt-secrets-into-aspnet-core-mvc-action-arguments-using-action-filters
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Decrypt Secrets Into ASP.NET Core MVC Action Arguments Using Action Filters
We’ve all been there before, needing to bridge the gap between a legacy system and our modern ASP.NET Core MVC application. I recently had an office hour with a great developer named Ramakrishna that needed to support a legacy security format. A legacy application encrypted all the values in a single query string parameter then made an HTTP request to an ASP.NET Core MVC endpoint.
This post will see how we can leverage Action Filters in ASP.NET Core MVC to make the development experience in our action methods the same one we’ve been using all this time.
The Problem
Legacy systems may not have access to all the protocols and security patterns we have today, so they might have had to do some creative problem-solving. In our case, we have a query string parameter that is encrypted using TripleDES, and we expect the receiver to decrypt the value.
/?secret=1w58y%2BC60jon25f%2F4VvVHUOX%2FIxs%2FEVx
This technique is likely because secrets in URLs might end up in logs. Whatever the reason, we have a problem to solve.
Our receiving endpoint will have two parameters of number
and name
that we expect to have values by the time we’re in the context of our action.
[Route(""), HttpPost]
public IActionResult IndexPost(int number, string name)
{
return View("Index",
new IndexModel
{
Number = number,
Name = name
});
}
We have a straightforward solution to this problem, and it involves Action Filters.
A Solution Using Action Filters and Action Arguments
Action Filters is a great way to intercept incoming requests and enhance the execution pipeline. In our case, we want to transform an existing query string parameter (secret
), decrypt its values, and set the parameters on the action method.
Our final usage will use an implemented EncryptedParameters
attribute, which we’ll decorate on applicable methods.
[Route(""), HttpPost]
[EncryptedParameters("secret")]
public IActionResult IndexPost(int number, string name)
{
return View("Index",
new IndexModel
{
Number = number,
Name = name
});
}
Let’s look at the implementation of EncryptedParameters
.
using System;
using System.Globalization;
using System.Linq;
using System.Web;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Secrets.Models;
namespace Secrets.Controllers
{
public class EncryptedParameters : ActionFilterAttribute
{
public string ParameterName { get; }
public EncryptedParameters(string parameterName = "secret")
{
ParameterName = parameterName;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
var config = context.HttpContext.RequestServices.GetRequiredService<IOptions<CryptoEngine.Secrets>>();
var encrypted = context.HttpContext.Request.Query[ParameterName].FirstOrDefault();
// decrypt secret
var decrypted = CryptoEngine.Decrypt(encrypted, config.Value.Key);
var collection = HttpUtility.ParseQueryString(decrypted);
var actionParameters = context.ActionDescriptor.Parameters;
foreach (var parameter in actionParameters)
{
try
{
var value = collection[parameter.Name];
if (value == null)
continue;
// set the action arguments to the values
// from the encrypted parameter
context.ActionArguments[parameter.Name] =
ConvertToType(value, parameter.ParameterType);
}
catch (Exception e)
{
context.ModelState.TryAddModelException(parameter.Name, e);
}
}
}
private static object? ConvertToType(string value, Type type)
{
var underlyingType = Nullable.GetUnderlyingType(type);
if (value.Length > 0)
{
if (type == typeof(DateTimeOffset) || underlyingType == typeof(DateTimeOffset))
{
return DateTimeOffset.Parse(value, CultureInfo.InvariantCulture);
}
if (type == typeof(DateTime) || underlyingType == typeof(DateTime))
{
return DateTime.Parse(value, CultureInfo.InvariantCulture);
}
if (type == typeof(Guid) || underlyingType == typeof(Guid))
{
return new Guid(value);
}
if (type == typeof(Uri) || underlyingType == typeof(Uri))
{
if (Uri.TryCreate(value, UriKind.RelativeOrAbsolute, out var uri))
{
return uri;
}
return null;
}
}
else
{
if (type == typeof(Guid))
{
return default(Guid);
}
if (underlyingType != null)
{
return null;
}
}
if (underlyingType is not null)
{
return Convert.ChangeType(value, underlyingType);
}
return Convert.ChangeType(value, type);
}
}
}
Using the ActionExecutingContext
, we have access to the parameters of our target endpoint. Then, it’s only a matter of decrypting our secret, and then setting the ActionArguments
using our known parameters.
public override void OnActionExecuting(ActionExecutingContext context)
{
var config = context.HttpContext.RequestServices.GetRequiredService<IOptions<CryptoEngine.Secrets>>();
var encrypted = context.HttpContext.Request.Query[ParameterName].FirstOrDefault();
// decrypt secret
var decrypted = CryptoEngine.Decrypt(encrypted, config.Value.Key);
var collection = HttpUtility.ParseQueryString(decrypted);
var actionParameters = context.ActionDescriptor.Parameters;
foreach (var parameter in actionParameters)
{
try
{
var value = collection[parameter.Name];
if (value == null)
continue;
// set the action arguments to the values
// from the encrypted parameter
context.ActionArguments[parameter.Name] =
ConvertToType(value, parameter.ParameterType);
}
catch (Exception e)
{
context.ModelState.TryAddModelException(parameter.Name, e);
}
}
}
The conversion of our types handles primitives and nullable types but does not handle complex models. Feel free to modify this code to meet your particular needs.
For folks interested in the CryptoEngine
code, here it is, but I do not consider myself a cryptography expert.
using System;
using System.Security.Cryptography;
using System.Text;
namespace Secrets.Models
{
/// <summary>
/// modified from the following post
/// https://dotnetcodr.com/2015/10/23/encrypt-and-decrypt-plain-string-with-triple-des-in-c/
/// </summary>
public static class CryptoEngine
{
public class Secrets
{
public string Key { get; set; }
}
public static string Encrypt(string source, string key)
{
var byteHash = MD5.HashData(Encoding.UTF8.GetBytes(key));
var tripleDes = new TripleDESCryptoServiceProvider
{
Key = byteHash,
Mode = CipherMode.ECB
};
var byteBuff = Encoding.UTF8.GetBytes(source);
return Convert.ToBase64String(tripleDes.CreateEncryptor()
.TransformFinalBlock(byteBuff, 0, byteBuff.Length));
}
public static string Decrypt(string encodedText, string key)
{
var byteHash = MD5.HashData(Encoding.UTF8.GetBytes(key));
var tripleDes = new TripleDESCryptoServiceProvider
{
Key = byteHash,
Mode = CipherMode.ECB
};
var byteBuff = Convert.FromBase64String(encodedText);
return Encoding.UTF8.GetString(
tripleDes
.CreateDecryptor()
.TransformFinalBlock(byteBuff, 0, byteBuff.Length));
}
}
}
In Practice
I created a simple ASP.NET Core MVC application to demonstrate this action filter in use. Starting with the view, we’ll encrypt some values and post them to our endpoint that uses the EncryptedParameters
attribute.
@model IndexModel
@inject Microsoft.Extensions.Options.IOptions<CryptoEngine.Secrets> config
@{
ViewData["Title"] = "Home Page";
var values = "name=Khalid&number=57";
var secret = CryptoEngine.Encrypt(values, config.Value.Key);
var url = Url.Action("IndexPost", new {secret});
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
<p>Click this link to transmit secret values via Querystring</p>
<form action="@url" method="POST">
<p>@url</p>
<button type="submit">Submit</button>
</form>
</div>
@if (Model != null)
{
<section style="margin-top: 1em">
<div class="text-center">
<h2>Secrets</h2>
<div>
<label asp-for="Name"></label>
@Model.Name
</div>
<div>
<label asp-for="Number"></label>
@Model.Number
</div>
</div>
</section>
}
When we submit the form, we receive our secrets on the page.
Conclusion
There you have it! We can use this technique to transform any incoming query string into any other set of parameters. This technique is powerful and can be applied to any MVC action or can be applied globally.
As always, use this code as a starting point and change it to meet your particular needs.
If you want to play with a working sample, you can find the code at my GitHub repository.
As always, thanks for reading, and please follow me on Twitter @buhakmeh.
Tags: aspnet
About Khalid Abuhakmeh
Khalid is a developer advocate at JetBrains focusing on .NET technologies and tooling.
Recommend
-
66
There are many encryption and decryption tools around. PowerShell is a Windows built-in tool and you can use it for cryptography as well. In this blog post I am going to play with encryption and decryption of data. My followers know what’s co...
-
3
Description April Edwards and Christopher Maneu are back for part two of this four-part series, Infra as Code in Action! Today, they'll be talking about secrets management! Is it really important to keep secrets? Yes! And th...
-
8
Reading Time: 3 minutes In Linux, you keep your data in form of files. But what if the data you are storing is sensitive. How can you protect that from unauthorized access? One of the ways is encrypting the files using GPG. In this blog, I’l...
-
28
r2flutch Yet another tool to decrypt iOS apps using r2frida. Requirements It requires to install Frida on the Jailbroken iOS device: Installation Using PIP: pip install...
-
5
Introduction Data security is an important aspect of the digital world. It helps to prevent data breaches, reduces the risk of data exposure, secures sensitive data, and is highly recommended for the regulatory compliance process. Wi...
-
6
Yogananda Muthaiah April 23, 2022 4 minute read
-
10
Encrypt and Decrypt Data in NodeJS using Crypto 1338 views 7 months ago NodeJS We will share with you in this articl...
-
4
asp.net architecture Action filters in ASP.NET - A quick guide...
-
6
Apple Facing Class Action Lawsuit Over Lack of Filters in MacBooks and iMacs Causing Trapped Dust MacR...
-
5
[Case study] Decrypt strings using Dumpulator Original text by m4n0w4r 1...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK