CSRF processing method in asp.net core

Time:2019-11-26

Preface

A few days ago, a friend asked me about the anti forgery token. Because I didn’t have a deep understanding of this part, I went to study it and sort it out.

Before combing, we need to have a brief understanding of the background knowledge.

Anti forgery token can be said to be a processing scheme for handling / preventing CSRF.

So what is CSRF?

CSRF (Cross Site Request Forgery) is cross site request forgery, also known as one click attack or session riding, usually abbreviated as CSRF or xsrf, which is a malicious use of the website.

Simply put: someone has stolen your identity and sent a malicious request in your name.

In recent years, CSRF has been in a low temperature and low fire position, but we still need to take precautions against this!

For more details, please refer to Wikipedia: cross site request Forge

From the perspective of use, I will analyze the handling of CSRF in asp.net core. I think there are two main parts

  • View level
  • Controller

Level view level

usage


@Html.AntiForgeryToken()

At the view level, it’s relatively simple to use. It’s htmlhelper. Add this sentence to the form form.

Principle analysis

When the above code is added to the form, the page will generate a hidden field. The value of the hidden field is a generated token, similar to the following example


<input name="__RequestVerificationToken" type="hidden" value="CfDJ8FBn4LzSYglJpE6Q0fWvZ8WDMTgwK49lDU1XGuP5-5j4JlSCML_IDOO3XDL5EOyI_mS2Ux7lLSfI7ASQnIIxo2ScEJvnABf9v51TUZl_iM2S63zuiPK4lcXRPa_KUUDbK-LS4HD16pJusFRppj-dEGc" />

Among themname="__RequestVerificationToken"Is a const variable defined,value=XXXXXIt is the result of Base64 encoding based on a pile of things and simple processing of Base64 encoded content. For specific implementation, please refer to base64urltextencoder.cs

The code to generate the above hidden domain is in the antiforgeryextensions file, the source file on GitHub: antiforgeryextensions.cs

The key methods are as follows:


public void WriteTo(TextWriter writer, HtmlEncoder encoder)
{
 writer.Write("<input name=\"");
 encoder.Encode(writer, _fieldName);
 writer.Write("\" type=\"hidden\" value=\"");
 encoder.Encode(writer, _requestToken);
 writer.Write("\" />");
}

Quite clear!

Controller level

usage

[ValidateAntiForgeryToken]
[AutoValidateAntiforgeryToken]
[IgnoreAntiforgeryToken]

All three can be based on classes or methods, so we just need to add these attributes to a controller or an action.


[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]

Principle analysis

Essentially filter, verify the value of the hidden field above

Filter implementation: validateantiforgerytokenauthorizationfilter and autovalidateantiforgerytokenauthorizationfilter

Where autovalidateantiforgerytokenauthorizationfilter inherits validateantiforgerytokenauthorizationfilter and only overrides the shouldvalidate method.

Here is the core method of validateaniforgerytokenauthorizationfilter:


public class ValidateAntiforgeryTokenAuthorizationFilter : IAsyncAuthorizationFilter, IAntiforgeryPolicy
{
 public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
 {
 if (context == null)
 {
 throw new ArgumentNullException(nameof(context));
 }

 if (IsClosestAntiforgeryPolicy(context.Filters) && ShouldValidate(context))
 {
 try
 {
 await _antiforgery.ValidateRequestAsync(context.HttpContext);
 }
 catch (AntiforgeryValidationException exception)
 {
 _logger.AntiforgeryTokenInvalid(exception.Message, exception);
 context.Result = new BadRequestResult();
 }
 }
 }
}

For the complete implementation, see GitHub source code: validateantiforgerytokenauthorizationfilter.cs

Of course, the filter here is just an entry, and the relevant verification is not implemented here. In fact, this module may be more appropriate on the antiforgery project.

Because it is interface oriented programming, to know the specific implementation, we need to find the corresponding implementation class.

In the antiforgery project, there is such an extension method, antiforgery servicecollectionextensions, which tells us that the corresponding implementation is defaultantiforgery. In fact, the source code of Nancy has been read a lot. If you look at the naming of a class, you should know that there are eight or nine parts to it.


 services.TryAddSingleton<IAntiforgery, DefaultAntiforgery>();

Iservicecollection is also involved, but this is not the focus of this article, so we will not talk about this, just point out that it is an important point in. Net core.

Well, get back to the point! To verify whether it is a legitimate request, it is natural to get the content to be verified first.


 var tokens = await _tokenStore.GetRequestTokensAsync(httpContext);

It is to get a cookie with the specified prefix of. Aspnetcore. Antivirus. From the cookie, and make the following judgment according to the cookie. The following is the specific implementation of verification:

public bool TryValidateTokenSet(
 HttpContext httpContext,
 AntiforgeryToken cookieToken,
 AntiforgeryToken requestToken,
 out string message)
{
 //Removed some non empty judgments

 // Do the tokens have the correct format?
 if (!cookieToken.IsCookieToken || requestToken.IsCookieToken)
 {
 message = Resources.AntiforgeryToken_TokensSwapped;
 return false;
 }

 // Are the security tokens embedded in each incoming token identical?
 if (!object.Equals(cookieToken.SecurityToken, requestToken.SecurityToken))
 {
 message = Resources.AntiforgeryToken_SecurityTokenMismatch;
 return false;
 }

 // Is the incoming token meant for the current user?
 var currentUsername = string.Empty;
 BinaryBlob currentClaimUid = null;

 var authenticatedIdentity = GetAuthenticatedIdentity(httpContext.User);
 if (authenticatedIdentity != null)
 {
 currentClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(httpContext.User));
 if (currentClaimUid == null)
 {
 currentUsername = authenticatedIdentity.Name ?? string.Empty;
 }
 }

 // OpenID and other similar authentication schemes use URIs for the username.
 // These should be treated as case-sensitive.
 var comparer = StringComparer.OrdinalIgnoreCase;
 if (currentUsername.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
 currentUsername.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
 {
 comparer = StringComparer.Ordinal;
 }

 if (!comparer.Equals(requestToken.Username, currentUsername))
 {
 message = Resources.FormatAntiforgeryToken_UsernameMismatch(requestToken.Username, currentUsername);
 return false;
 }

 if (!object.Equals(requestToken.ClaimUid, currentClaimUid))
 {
 message = Resources.AntiforgeryToken_ClaimUidMismatch;
 return false;
 }

 // Is the AdditionalData valid?
 if (_additionalDataProvider != null &&
 !_additionalDataProvider.ValidateAdditionalData(httpContext, requestToken.AdditionalData))
 {
 message = Resources.AntiforgeryToken_AdditionalDataCheckFailed;
 return false;
 }

 message = null;
 return true;
}

Note:There is also a process of deserialization before validation, which is to get the cookie token and requesttoken to be judged from the cookie

How to use

In the previous section, we briefly introduced the internal implementation, and then we will use a simple example to see the specific usage:

Use 1: regular form

Add a form to the view first


<form action="/home/antiform" method="post">
 @Html.AntiForgeryToken()
 <p><input type="text" name="message" /></p>
 <p><input type="submit" value="Send by Form" /></p>
</form>

Add an action to the controller


[ValidateAntiForgeryToken]
[HttpPost]
public IActionResult AntiForm(string message)
{
 return Content(message);
}

Let’s see if the generated HTML, as we said earlier, will@Html.AntiForgeryToken()Output as a name__RequestVerificationTokenHidden fields for:

Take a look at the information about cookies:

As you can see, everything is still carried out as mentioned above. Input information in the input box and click the button to display the text we input normally.

Use two: Ajax commit

Form:


<form action="/home/antiajax" method="post">
 @Html.AntiForgeryToken()
 <p><input type="text" name="message" /></p>
 <p><input type="button" value="Send by Ajax" /></p>
</form>

js:


$(function () {
 $("#btnAjax").on("click", function () {
 $("#form2").submit(); 
 });
})

This way of writing is the same as the result above!

I’m afraid of the following:


$.ajax({
 type: "post",
 dataType: "html",
 url: '@Url.Action("AntiAjax", "Home")',
 data: { message: $('#ajaxMsg').val() },
 success: function (result) {
 alert(result);
 },
 error: function (err, scnd) {
 alert(err.statusText);
 }
});

In this way, we can’t see any problems under normal circumstances, but it is the following result (400 errors):

I believe everyone has found out the problem!! The related content of hidden domain has not been posted together!!

There are two ways to deal with it:

Method 1:

Add the content related to the hidden domain in data, which is roughly as follows:


$.ajax({
 // 
 data: { message: $('#ajaxMsg').val(), __RequestVerificationToken: $("input[name='__RequestVerificationToken']").val()}
});

Method two:

Add a header to the request


$("#btnAjax").on("click", function () {
 var token = $("input[name='__RequestVerificationToken']").val();
 $.ajax({
 type: "post",
 dataType: "html",
 url: '@Url.Action("AntiAjax", "Home")',
 data: { message: $('#ajaxMsg').val() },
 headers:
 {
  "RequestVerificationToken": token
 },
 success: function (result) {
  alert(result);
 },
 error: function (err, scnd) {
  alert(err.statusText);
 }
 });
});

So we can deal with the problems above!

Use 3: Customize related information

Many people may think that the name of the generated hidden domain can be changed into their own, and the cookie name can be changed into their own ~

The answer is yes. Here is a simple demonstration:

In the configureservices method of startup, add the following to modify the default name accordingly.


services.AddAntiforgery(option =>
{
 option.CookieName = "CUSTOMER-CSRF-COOKIE";
 option.FormFieldName = "CustomerFieldName";
 option.HeaderName = "CUSTOMER-CSRF-HEADER";
});

Accordingly, the Ajax request also needs to be modified:

Var token = $("input [name ='customerfieldname ']). Val(); // the name of the hidden domain needs to be changed
$.ajax({
 type: "post",
 dataType: "html",
 url: '@Url.Action("AntiAjax", "Home")',
 data: { message: $('#ajaxMsg').val() },
 headers:
 {
 "Customer-csrf-header": token // note that the header needs to be modified
 },
 success: function (result) {
 alert(result);
 },
 error: function (err, scnd) {
 alert(err.statusText);
 }
});

Here are the effects:

Form form:

Cookie:

Related projects involved in this paper:

  • Mvc
  • Antiforgery
  • HttpAbstractions

About CSRF

Preventing Cross-Site Request Forgery (XSRF/CSRF) Attacks in ASP.NET Core

On the attack mode of CSRF

summary

The above is the whole content of this article. I hope that the content of this article has a certain reference learning value for everyone’s study or work. If you have any questions, you can leave a message and exchange. Thank you for your support for developepaar.