In-Depth

Protect ASP.NET Applications Against CSRF Attacks

Protect your ASP.NET applications from Cross-Site Request Forgery attacks by leveraging ARMOR, a C# implementation of the Encrypted Token Pattern. Here's how.

The Encrypted Token Pattern is a defense mechanism against Cross-Site Request Forgery (CSRF) attacks, which are Web site exploits that attackers can use to transmit commands from a trusted site. The Encrypted Token Pattern is an alternative to the Synchroniser Token Pattern and Double Submit Cookie Pattern, all of which have the same objectives:

  • To ensure that any given HTTP request originates from a trustworthy source
  • To uniquely identify the user issuing the HTTP request

In the first instance, the need to ensure that requests originate from a trustworthy source is an obvious requirement. Essentially, you need to guarantee a request originates not only from the user's Web browser, but also from a non-malicious link or connection.

Consider a banking application. Suppose that application exposes an API that allows the transfer of funds between accounts as follows:

http://mybank.com/myUserId/fromAccount/toAccount/amount

Web browsers share state, in terms of cookies, across tabs. Imagine a user that's logged into mybank.com. He opens a new tab in a browser and navigates to a Web site containing a link to that URI. An attacker who knows that user's bank account number could potentially make transfers from the user's account to his own.

Remember, the user is already logged in to mybank.com at this point and has an established session on the Web server, if not a persistent cookie in his Web browser. The hacker simply opens a new tab in his browser, leverages the user's logged-in credentials, and executes the HTTP request on the user's behalf.

Mounting a Defense
In order to defend against such attacks, you need to introduce a token on the user's behalf and validate that token on the Web server during HTTP requests. This ensures each request originates from a trusted source and uniquely identifies the user.

Why do you want to uniquely identify the user? Consider that CSRF attacks can potentially originate from valid users.

I'll explore a more sophisticated attack. John and David are both valid users of mybank.com. John decides to post a malicious link. John builds a small Web server that issues an HTTP request to the mybank.com money-transfer API:

http://mybank.com/myUserId/fromAccount/toAccount/amount

This time, John supplies a valid token: his own (remember, John is also a valid user). Now, assuming mybank.com doesn't validate the identity of the user in the supplied token, it will determine that the request originates from a trusted source, and allows the transfer to take place.

How Encrypted Token Pattern Works
The Encrypted Token Pattern protects Web applications against CSRF attacks by generating a secure token at the server level, and issuing the token to the client. The token itself is essentially a JSON Web Token (JWT) composed of a unique User ID, a randomly generated number (nonce), and a timestamp. Given the token is a JSON object, it's possible to include any additional metadata in the token. The process flow is shown in Figure 1.

[Click on image for larger view.] Figure 1. Encrypted Token Pattern Process Flow

The Advanced Resilient Mode of Recognition (ARMOR) is a C# implementation of the Encrypted Token Pattern, available on GitHub under the MIT license. It provides a means of protecting ASP.NET applications from CSRF attacks by leveraging the Encrypted Token Pattern. The following describes a typical setup configuration.

ARMOR: ARMOR is a framework composed of interconnecting components exposed through custom DelegatingHandler and AuthorizationAttribute classes. ARMOR is essentially an advanced encryption and hashing mechanism, leveraging the Rijndael encryption standard, and SHA256 hashing by default, though these are concrete implementations. ARMOR provides abstractions in terms of encryption, allowing developers to leverage custom concrete implementations. ARMOR has two primary directives:

  1. To generate secure ARMOR tokens
  2. To validate secure ARMOR tokens

ARMOR Web Framework: The ARMOR Web Framework is a set of components that leverage ARMOR itself, allowing developers to leverage the ARMOR framework in a plug-and-play fashion, without necessarily grappling with the underlying complexities of encryption and hashing. This tutorial focuses on leveraging the ARMOR Web Framework in C# to protect your ASP.NET applications from CSRF attacks.

Leveraging ARMOR in ASP.NET
Download the ARMOR Web Framework package from NuGet:

PM> Install-Package Daishi.Armor.WebFramework

Then add the following configuration settings to your web.config file:

<add key="IsArmed" value="true" />; <add key="ArmorEncryptionKey" value="{Encryption Key}"/>; <add key="ArmorHashKey" value="{Hashing Key}"/>; <add key="ArmorTimeout" value="1200000"/>;

IsArmed is a toggle feature easily allowing developers to turn ARMOR on or off.

ArmorEncryptionKey is an encryption key that ARMOR will use to both encrypt and decrypt ARMOR tokens.

ArmorHashKey is the hashing key that ARMOR will use to generate and validate hashes contained within ARMOR tokens. ARMOR implements hashes as a means of determining whether tokens have been tampered with, and to add an extended level of entropy to token metadata, rendering them more difficult to hijack.

ArmorTimeout is the time in milliseconds that ARMOR Tokens remain valid.

In order to facilitate encryption and hashing, ARMOR requires two keys. You can generate both keys as follows:

byte[] encryptionKey = new byte[32]; byte[] hashingKey = new byte[32]; using (var provider = new RNGCryptoServiceProvider()) {   provider.GetBytes(encryptionKey);   provider.GetBytes(hashingKey); }

These keys must be stored in the ArmorEncryptionKey and ArmorHashKey values in your configuration file, in Base64-format.

To hook the ARMOR Filter to your application, use the following core components:

Authorization Filter: The Authorization filter reads the ARMOR Token from the HttpRequest Header and validates it against the currently logged-in user. Users can be authenticated in any fashion; ARMOR assumes that your user's Claims are loaded into the current Thread at the point of validation. It uses two classes, MvcArmorAuthorizeAttribute and WebApiArmorAuthorizeAttribute, to facilitate authorization for both MVC and Web API projects, respectively.

Fortification Filter: The Fortification filter refreshes and reissues new ARMOR tokens. It uses the MvcArmorFortifyFilter and WebApiArmorFortifyFilter classes to facilitate fortification for both MVC and Web API projects, respectively.

Generally speaking, it's ideal that you refresh the incoming ARMOR token for every HTTP request, whether that request validates the Token, and particularly for Get HTTP requests. Otherwise, the token may expire unless the user issues a Post, Put or Delete requests within the token's lifetime.

To do this, simply register the appropriate ARMOR Fortification mechanism in your MVC application:

public static void RegisterGlobalFilters(GlobalFilterCollection filters) {   // Existing filters   filters.Add(new MvcArmorFortifyFilter()); }

or in your Web API application:

config.Filters.Add(new WebApiArmorFortifyFilter());

Now, each HttpResponse issued by your application will contain a custom ARMOR Header containing a new ARMOR token for use with subsequent HTTP requests, as shown in Figure 2.

[Click on image for larger view.] Figure 2. Each HttpResponse Will Contain Custom ARMOR Header with a New ARMOR Token for Use with Subsequent HTTP Requests

Decorating Post, Put and Delete Endpoints with ARMOR: In an MVC Controller, simply decorate your endpoints as follows:

[MvcArmorAuthorize]

and in Web API Controllers:

[WebApiArmorAuthorize]

Integrating Your Application's Authentication Mechanism: AMROR operates on the basis of Claims and provides default implementations of Claim-parsing components derived from the IdentityReader class in the following classes:

  • MvcIdentityReader
  • WebApiIdentityReader

Both classes return an enumerated list of Claim objects consisting of a UserId Claim. In the case of MVC, the Claim is derived from the ASP.NET intrinsic Identity.Name property, assuming the user is already authenticated. In the case of Web API, it's assumed you leverage an instance of ClaimsIdentity as your default IPrincipal object, and that user metadata is stored in Claims held within that ClaimsIdentity. As Such, the WebApiIdentityReader simply extracts the UserId Claim. Both UserId and Timestamp Claims are the only default Claims in an ArmorToken and are loaded upon creation.

If your application leverages a different authentication mechanism, you can simply derive from the default IdentityReader class with your own implementation and extract your logged-in user's metadata, injecting it into Claims necessary for ARMOR to manage. The default Web API implementation is shown here:

public override bool TryRead(out IEnumerable<Claim> identity) {   var claims = new List<Claim>();   identity = claims;
  var claimsIdentity = principal.Identity as ClaimsIdentity;   if (claimsIdentity == null) return false;
  var subClaim = claimsIdentity.Claims.SingleOrDefault(c => c.Type.Equals("UserId"));
  if (subClaim == null) return false;
  claims.Add(subClaim);
  return true;
}

ARMOR downcasts the intrinsic HTTP IPrincipal.Identity object as an instance of ClaimsIdentity and extracts the UserId Claim. Deriving from the IdentityReader base class allows you to implement your own mechanism to build Claims.

It's worth noting that you can store as many Claims as you like in an ARMOR token. ARMOR will decrypt and deserialize your Claims so they can be read on the return journey back to the server from the UI.

Adding ARMOR UI Components
The ARMOR WebFramework contains the following JavaScript file:

var ajaxManager = ajaxManager || {   setHeader: function(armorToken) {     $.ajaxSetup({       beforeSend: function(xhr, settings) {         if (settings.type !== "GET") {           xhr.setRequestHeader("Authorization", "ARMOR" + armorToken);         }       }     });   } };

The purpose of this code is to detect the HttpRequest type, and apply an ARMOR Authorization Header for Post, Put and Delete requests. You can leverage this on each page of your application (or in the default Layout page) as follows:

<script>   $(document).ready(function () {     ajaxManager.setHeader($("#armorToken").val());   });   $(document).ajaxSuccess(function (event, xhr, settings) {     var armorToken = xhr.getResponseHeader("ARMOR") || $("#armorToken").val();     ajaxManager.setHeader(armorToken);   }); </script>

As you can see, the UI contains a hidden field called "armorToken." This field needs to be populated with an ArmorToken when the page is initially served. The code in the ARMOR API itself facilitates this, as shown in Listing 1.

Listing 1: The ARMOR API
var nonceGenerator = new NonceGenerator(); nonceGenerator.Execute();
var encryptionKey =   
  Convert.FromBase64String(ConfigurationManager.AppSettings["ArmorEncryptionKey"]);
var hashingKey = 
  Convert.FromBase64String(ConfigurationManager.AppSettings["ArmorHashKey"]);
var armorToken = new ArmorToken(User.Identity.Name, "MyApp", nonceGenerator.Nonce);
var armorTokenConstructor = new ArmorTokenConstructor();
var standardSecureArmorTokenBuilder = 
  new StandardSecureArmorTokenBuilder(armorToken, encryptionKey, hashingKey);
var generateSecureArmorToken = 
  new GenerateSecureArmorToken(armorTokenConstructor, standardSecureArmorTokenBuilder);
generateSecureArmorToken.Execute();
ViewBag.ArmorToken = generateSecureArmorToken.SecureArmorToken;

Here, the initial ARMOR token is generated to be served when the application loads. This token will be leveraged by the first AJAX request and refreshed on each subsequent request. The token is then loaded into the ViewBag object and absorbed by the associated View:

<div><input id="armorToken" type="hidden" [email protected] /></div>

Now your AJAX requests are decorated with ARMOR Authorization attributes, as shown in Figure 3.

[Click on image for larger view.] Figure 3. AJAX Requests with ARMOR Authorization Attributes

To Protect and Serve
Now that you've implemented the ARMOR WebFramework, each Post, Put and Delete request will persist a Rijndael-encrypted and SHA256-hashed ARMOR token, which is validated by the server before each Post, Put, or Delete request decorated with the appropriate attribute is handled, and refreshed after each request completes.

The simple UI components attach new ARMOR tokens to outgoing requests and read ARMOR Tokens on incoming responses. ARMOR is designed to work seamlessly with your current authentication mechanism to protect your application from CSRF attacks.

About the Author

Paul Mooney is a technology consultant proficient in C#, JavaScript, Java, and Golang, and a well-known software-development mentor. He also created the Encrypted Token Pattern and ARMOR, its .NET implementation. Contact Paul through his blog, http://insidethecpu.com/.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube