This post will serve as a guide about .NET XSS.
What is this and why does it matter? Well, developing an application and putting it out there, though not an easy task, is but the first step. The second—and potentially never-ending—step is to make sure it continues to work, meeting the users' needs in the best possible way.
If that sounds simple, it's anything but: there are many components involved, and security is one of the most important ones.
That's where XSS comes in. As one of the most common security threats, it's something you, as a developer, must be aware of. No programming language or stack is immune to it, and .NET surely is no exception to the rule.
We'll open this post with some fundamentals. You'll get a brief definition of XSS, followed by an explanation of the damage it causes and how it works. After that, we'll get to the .NET-specific portion of the post. You'll see an example of .NET XSS and tips to protect yourself.
Let's get started.
Requirements
This post will feature a practical portion. There are a few requirements if you want to follow along:
You'll need the SDK (software development kit) for .NET 6.0.
Also, I assume you're on Windows and using Visual Studio 2022. (Get the free community edition here if you don't have it.)
I expect you have some familiarity with .NET/C#.
.NET XSS Fundamentals
As promised, let's open the post with some basics by answering the what, why, and how of XSS, not only in .NET but in general.
What is XSS?
XSS stands for cross-site scripting. It's a type of injection attack. An injection attack, as the name suggests, is when a malicious agent is able to successfully introduce and run some unauthorized code into an application. This injected code interacts with the proper code of the app, making it behave in ways it shouldn't.
XSS attacks happen when malicious individuals are able to "trick" a legitimate website into unwillingly executing a malicious script that's provided by exploiting the usual mechanisms for user input—e.g., URL parameters or form fields. In short, the attacker exploits vulnerable sites that accept HTML code as input and then displays that without encoding it, and injects an unauthorized <script> tag in the hopes it will be accepted and executed.
Why Is XSS Dangerous?
In the worst-case scenario, a successful XSS attack can cause catastrophic damage to an application. By successfully executing the script, the attacker could gain access to browser cookies—which would allow them to perform actions or access data on behalf of the user—local storage, and more. The consequences can range from unauthorized data access to account stealing and even unauthorized financial transactions.
Keep in mind that XSS is often used together with—and facilitates—other types of attacks, such as CSRF.
How Does XSS Work?
Successful XSS attacks are usually due to websites blindly trusting all user input. I'll get back to this later, but it's never too much to reiterate: never trust user input!
Anyway, how does an XSS attack happen? Well, here's a quick example. Let's say you have a web app that takes an option as a URL parameter, such as this:
https://www.example.com/products.html?category=phone
Then, let's say the entered value gets displayed in the page's HTML:
<h2>Category: phone</h2>
What if the user entered something like this?
https://www.example.com/products.html?category=<script>alert('hello!');</script>
In this case, the site isn't protected against XSS. So, what would happen is that the script would be executed, and you'd see the hello message being displayed. And, of course, in real life, the attacker would enter some malicious excerpt of code instead of a benign message.
.NET XSS: How Is Your App Protected?
As is the case with several other security threats, .NET is already well defended against XSS, and you'd actually have to work a little bit to make yourself unprotected. To understand how that works, let's create a sample project.
Create a Sample Project
Start by firing up Visual Studio and clicking on Create a new project. Then, you'll be prompted to pick a project type. Choose ASP.NET Core Web App (Model-View-Controller), like in the following image:
On the next screen, enter a project name and location, and a name for the solution:
On the next screen, simply accept all of the defaults and click on Create. After Visual Studio finishes creating the application, perform a quick smoke test. Press F5, and your default browser should open and show you something like this:
Add a Model
The next step is to add a model. In Visual Studio, go to the Solution Explorer, right-click the Models folder, then go to Add and Class. Enter Todo as the name of the class and confirm. After you create the class, replace its contents with this:
namespace XSSNetDemo.Models
{
public class Todo
{
public int Id { get; set; }
public string? Description { get; set; }
public DateOnly DueDate { get; set; }
}
}
So, this sample application will be a big cliché: a to-do list. The class above represents a to-do item, with an ID, a description, and a due date. For the next step, we want to create the controller and views for our app.
Scaffold the Controller and Views
Go to the Solution Explorer and right-click the folder Controllers. Then, go to Add > New Scaffolded item.
On the next screen, pick MVC controller with views, using Entity Framework, and then click Add:
You'll then be prompted to enter a few important pieces of information.
First, you have to define which model you're generating a controller for. Pick the Todo class.
Then, you'll have to provide a database context for the controller. We don't have one, so click the plus sign and add a new context.
Finally, change the Controller name, as Visual Studio will use the pluralization rules and come up with TodoesController. I think that TodosController looks nicer, so I changed it to that.
As for the other options, leave them with their default values. Finish by clicking on Add.
Before running the application, let's make a small change so we can use an in-memory database. Go to the Program.cs class and remove the following line:
builder.Services.AddDbContext<XSSNetDemoContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("XSSNetDemoContext")));
Replace it with the following:
builder.Services.AddDbContext<XSSNetDemoContext>(options => options.UseInMemoryDatabase("todos"));
Finally, go to the Tools menu, then NuGet Package Manager > Package Manager Console. Paste and run the following command:
Install-Package Microsoft.EntityFrameworkCore.InMemory
Let's Run It
You're finally ready to run the app. Press F5 in Visual Studio. Your browser will open the app. Go to the address bar and append /Todos to the address. You should see something like this:
Click on Create New, and you'll be taken to a new screen, where you'll be able to add a new todo item:
Now, let's test whether we can inject some script into this app. On the due date line, enter whatever you want. But for the description, try to enter some script, like <script>alert('hey!');</script>, and then click on the Create button.
Luckily, the result is quite anti-climatic:
As you can see, the application simply displays the "malicious" script instead of executing it. How is this possible? Well, if you use your browser's feature of showing the page's underlying HTML code, you'll see something like this:
<tbody>
<tr>
<td>
<script>alert('oi');</script>
</td>
<td>
05/02/2022 12:17:00
</td>
<td>
<a href="/Todos/Edit/1">Edit</a> |
<a href="/Todos/Details/1">Details</a> |
<a href="/Todos/Delete/1">Delete</a>
</td>
</tr>
</tbody>
The code above shows that the less-than and greater-than signs from the <script> tag were replaced with those funny-looking codes. Those are HTML entities, which means the HTML tags were encoded. That way, the page can safely display them instead of running them.
Opening the App to XSS
For the sake of curiosity, let's say you want to be able to make the app vulnerable to XSS. How would you go about that?
Go to the Views folder. Locate the Todos folder, and then open the file called Index.cshtml.
Inside the file, locate this line:
@Html.DisplayFor(modelItem => item.Description)
Replace it with the following:
@Html.Raw(item.Description)
As the code suggests, now we're displaying the raw HTML without any type of encoding. Just to make things clear, this is terrible practice! Don't do it in real life.
Now, run the application again, inject the malicious script as the description again, and you'll see this:
Another Bad Example
Before we go, here's another example of what not to do. Go back to the TodosController.cs class and add the following method:
public string Introduce(string id)
{
return $"Hello, my name is {id}.";
}
Now, run the app again. Then, append /Todos/Introduce/<YOUR-NAME> to the address, making sure to use your actual name. You'll see something like this:
Now you know what to do. Replace the name with a script and voilà—it will be executed. How can you solve this problem? Simple:
public string Introduce(string id)
{
return HtmlEncoder.Default.Encode($"Hello, my name is {id}.");
}
Make sure to add using System.Text.Encodings.Web; to your list of usings and you're done. Now the code properly encodes the entered code for HTML, avoiding the interpretation and execution of scripts.
Conclusion
XSS is a security threat that can have devastating effects, but avoiding it is relatively simple. You have to remember to never trust user input and always encode the HTML code that is to be displayed.
Additionally, it helps if you don't allow the user to submit HTML at all—for rich-text formatting purposes, for instance. If there's a need, prefer a format such as markdown that you can then safely convert to HTML when it's time to display it.
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.