Creating an app and putting it out there isn't a walk in the park, but it's just the first step in a long—or should I say never-ending?—journey. Now that your app is out the door, you have to worry about a plethora of things. Are there bugs? Is the user experience good enough? What about performance?
Among all of those concerns, there's probably not a single one more pressing than security. Data has never been as valuable as it is today, and with the popularization of web apps, there has never been so much of it available. This is almost irresistible for malicious actors out there, so you need to ensure your applications remain safe.
This post is another installment in a series in which we cover some of the main security threats and how to prevent them using a variety of languages and frameworks. Today's combination of security threat and tech stack is .NET CSRF. We'll open the post by defining CSRF in general. Then, we'll advance into the specifics of how to prevent it in .NET.
Let's get started.
What Is CSRF? Why Should You Care?
Although we have a dedicated post about CSRF (you should really check it out), we'll start this post by briefly covering this security threat.
CSRF stands for cross-site request forgery. As the name suggests, this attack consists of an HTTP request sent from across a different site. The "forgery" part means the attack relies on an authenticated user being tricked into executing some malicious code. The code leverages the fact that the user is logged in—and, thus, his or her browser has an authentication cookie—to try and perform some unauthorized task.
A successful CSRF can have dramatic consequences. The malicious actor could gain access to unauthorized information, change the password or other information on the user's account, and even transfer monetary funds.
A Basic Example of CSRF
To understand how CSRF attacks are performed, you must understand that, at first, there's nothing stopping you from sending an HTTP request anywhere as long as you have the URL.
So, imagine a malicious actor wants to change a date in someone's account on a website. Let's say this person knows exactly the URL to send the request to. If they try to do it, their attempt will most likely fail. That's because the account settings portion of the website requires authentication.
Now, imagine that this malicious actor (I'm getting tired of repeating this; let's call him Jim)—imagine Jim successfully tricks his foe into clicking a button that sends the request to the website. Here's the key part: his foe—let's call him Dwight—is already logged into the website. As such, his browser has a valid cookie, which gets sent along with the request.
If the website doesn't have protection against CSRF, the damage is done.
How to Protect Your Application Against CSRF
A CSRF attack, despite being potentially catastrophic, is an old type of security threat, and most languages/frameworks already feature built-in protection against it. Regardless of tech stack, though, how does protection against CSRF work in practice?
The basic idea is to come up with a way to make requests impossible to predict by including in the request itself some type of information that can't be forged and changes with every request.
So, defending against CSRF usually relies on a token that's generated and included as a hidden field in the HTML code of web forms. This value is sent with each request and validated on the server's side.
.NET CSRF Protection: How to Use It
As I said earlier, many languages and frameworks nowadays already come with built-in protection against CSRF attacks, since it's so common of a threat. Let's see how to activate it and use it in practice in some different scenarios.
Requirements
This tutorial will be fairly simple, but there are some requirements if you want to follow along:
I'll assume you're on Windows.
You'll need to install Visual Studio 2022 (download the free Community edition here).
You'll need Postman or a similar tool.
Create a Sample Project
Using Visual Studio, we'll start a new web application. Open Visual Studio and click on Create a new project:
You'll then see a new screen:
Pick C# as the language.
Choose "All platforms."
Pick "Web" as the target.
Choose "ASP.NET Core Web App (Model-View-Controller)" as the project template.
Finally, click on Next. On the next screen, pick a name and location for the project:
You'll be taken to the last screen. Here, simply leave all options as the defaults and click on Create:
Next Step: Adding More Code
We now have the skeleton of an MVC application. Let's add a bit of code to it.
First, we'll create a model. On Solution Explorer, right-click the Models folder. Then, go to Add, and then Class:
Name the class Person and click on Add:
Add the following code to the file:
namespace CSRFDemo.Models
{
public class Person
{
public int Id { get; set; }
public string? Name { get; set; }
public int Age { get; set; }
}
}
The next step is to use some Visual Studio magic to generate a whole bunch of code. Go back to Solution Explorer, right-click Controllers, then Add, then New Scaffolded Item:
On the new screen, pick MVC Controller with views, using Entity Framework as the type of item:
Click on Add. You'll then be prompted for a few configuration details:
Choose the model class for the generated items. Pick the Person class you've just created.
You'll need a data context class. Click on the plus sign next to the selection control to create a new one.
Finally, click on Add.
After that, Visual Studio will create the controller along with several views. We'll now add just two tiny changes to the application so we can use an in-memory database. First of all, let's install a NuGet package. Go to Tools -> NuGet Package Manager -> Package Manager Console.
Then, run the following command:
Install-Package Microsoft.EntityFrameworkCore.InMemory.
Next, go to Program.cs and add the following line:
builder.Services.AddDbContext<CSRFDemoContext>(options => options.UseInMemoryDatabase("demo")).
Now, you can run the application (pressing F5), then go to https://localhost:7271/People, and you'll see something like this:
Testing CSRF
Now let's test whether it's possible to send a request from outside the application. With the application running, add a few items. Then, click on Delete so you can get to the delete page. Identify the ID of the item you're trying to delete.
Now, let's use Postman. Create a request that sends a post to the endpoint used for deletion. Example:
As you can see, I got a bad request. That's proof the anti-CSRF facilities are working. Now, let's deactivate them.
To do that, we just have to go to the PeopleController class and locate the Delete method. More specifically, the delete confirmation method:
// POST: People/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var person = await _context.Person.FindAsync(id);
_context.Person.Remove(person);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
As you can see, the method is decorated with a [ValidateAntiForgeryToken] attribute. Comment it out or remove it, and then use Postman to send the request again. This time, you should see a success screen.
Conclusion
CSRF is one of the most common and pervasive security threats. On the bright side, nowadays it's common for most major languages/frameworks to come with built-in protection against this kind of attack. C#/.NET is no exception.
Thanks for reading!
This post was written by Carlos Schults. Carlos is a consultant and software engineer with experience in desktop, web, and mobile development. Though his primary language is C#, he has experience with a number of languages and platforms. His main interests include automated testing, version control, and code quality.