Md Mominul Islam | Software and Data Enginnering | SQL Server, .NET, Power BI, Azure Blog

while(!(succeed=try()));

LinkedIn Portfolio Banner

Latest

Home Top Ad

Responsive Ads Here

Post Top Ad

Responsive Ads Here

Tuesday, September 2, 2025

Preventing XSS and CSRF Attacks in ASP.NET Core MVC Applications

 

Introduction: Why XSS and CSRF Protection is Crucial

Imagine you're building an online task management app where users create and share tasks. A malicious user injects a script into a task description, stealing other users’ session cookies, or tricks a user into unknowingly deleting tasks via a forged request. These are Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF) attacks—common web vulnerabilities that can compromise your ASP.NET Core MVC application.

XSS allows attackers to inject malicious scripts into web pages viewed by other users, potentially stealing data or hijacking sessions. CSRF tricks authenticated users into executing unwanted actions on your app. Both can lead to data breaches, unauthorized actions, or reputational damage.


Let’s lock down your app against XSS and CSRF!

Section 1: Understanding XSS and CSRF Attacks

What is Cross-Site Scripting (XSS)?

XSS occurs when an attacker injects malicious scripts (e.g., JavaScript) into a web page, which then executes in a user’s browser. This can steal cookies, manipulate the DOM, or redirect users to malicious sites.

Types of XSS:

  • Stored XSS: Malicious script is stored in the database (e.g., in a task description) and executed when users view the page.
  • Reflected XSS: Script is embedded in a URL or input and executed immediately (e.g., via a crafted link).
  • DOM-Based XSS: Script manipulates the DOM client-side without server interaction.

Real-World Example: A user submits a task description with <script>alert('Hacked!');</script>. If displayed unencoded, it runs in every user’s browser.

What is Cross-Site Request Forgery (CSRF)?

CSRF tricks an authenticated user into performing actions on your app without their knowledge, using their active session. For example, a malicious site sends a POST request to delete a task.

Real-World Example: A user visits a malicious site while logged into your app. The site sends a forged request to POST /tasks/delete/1, deleting a task.

Pros of Preventing XSS and CSRF:

  • User Trust: Protects user data and maintains app integrity.
  • Compliance: Meets security standards like OWASP and GDPR.
  • Robustness: Prevents unauthorized actions and data leaks.

Cons:

  • Complexity: Adds development overhead for validation and token management.
  • Performance: Encoding and token validation introduce slight latency.
  • Maintenance: Requires ongoing updates to address new attack vectors.

Alternatives:

  • Content Security Policy (CSP): Mitigates XSS by restricting script sources (complements encoding).
  • Same-Site Cookies: Reduces CSRF risk by limiting cross-site cookie usage.
  • API-Only Architecture: Use tokens (e.g., JWT) instead of session-based CSRF tokens for SPAs.

Best Practices (Preview):

  • Encode all user input when rendering to prevent XSS.
  • Use ASP.NET Core’s built-in CSRF protection for forms.
  • Follow OWASP guidelines: validate inputs, use secure cookies, and implement CSP.
  • Regularly test for vulnerabilities using tools like OWASP ZAP.

Section 2: Preventing XSS in ASP.NET Core MVC

Step 1: Set Up the Project

Create a task management MVC app with Entity Framework Core.

Install required packages:

bash
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design

Define a TaskItem model:

csharp
public class TaskItem
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public bool IsCompleted { get; set; }
}

Set up TaskContext:

csharp
using Microsoft.EntityFrameworkCore;
public class TaskContext : DbContext
{
public TaskContext(DbContextOptions<TaskContext> options) : base(options) { }
public DbSet<TaskItem> Tasks { get; set; }
}

Configure DI in Program.cs:

csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TaskContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddControllersWithViews();
var app = builder.Build();
app.UseStaticFiles();
app.UseRouting();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Tasks}/{action=Index}/{id?}");
app.Run();

appsettings.json:

json
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=TaskDb;Trusted_Connection=True;"
}
}

Apply migrations:

bash
dotnet ef migrations add InitialCreate
dotnet ef database update

Step 2: Preventing Stored XSS

ASP.NET Core’s Razor engine automatically encodes output, preventing XSS by default. However, improper handling can still introduce vulnerabilities.

Vulnerable Code (Don’t Do This):

csharp
// TasksController.cs
public class TasksController : Controller
{
private readonly TaskContext _context;
public TasksController(TaskContext context)
{
_context = context;
}
public IActionResult Index()
{
var tasks = _context.Tasks.ToList();
return View(tasks);
}
}

html
<!-- Views/Tasks/Index.cshtml -->
@model IEnumerable<TaskItem>
@foreach (var task in Model)
{
<div>@Html.Raw(task.Description)</div> <!-- Vulnerable: Html.Raw bypasses encoding -->
}

If a user submits <script>alert('Hacked!');</script> as a description, it executes in users’ browsers.

Secure Code:

html
<!-- Views/Tasks/Index.cshtml -->
@model IEnumerable<TaskItem>
@foreach (var task in Model)
{
<div>@task.Description</div> <!-- Razor auto-encodes, preventing XSS -->
}

Explanation: Razor escapes HTML characters (e.g., < becomes &lt;), rendering scripts as plain text. Avoid Html.Raw unless you explicitly trust the content.

Interactive Challenge: Submit a malicious description like <script>alert('XSS');</script> to the app. Check the rendered page—does the script execute or display as text?

Step 3: Preventing Reflected XSS

Reflected XSS occurs when user input (e.g., query strings) is directly rendered without encoding.

Vulnerable Code:

csharp
public IActionResult Search(string query)
{
ViewBag.Query = query; // No encoding
return View();
}

html
<!-- Views/Tasks/Search.cshtml -->
<div>Search: @Html.Raw(ViewBag.Query)</div> <!-- Vulnerable -->

A URL like /Tasks/Search?query=<script>alert('Hacked!');</script> could execute.

Secure Code:

html
<div>Search: @ViewBag.Query</div> <!-- Auto-encoded -->

Best Practice: Always rely on Razor’s encoding. Validate query inputs server-side:

csharp
public IActionResult Search(string query)
{
if (string.IsNullOrWhiteSpace(query) || query.Contains("<script"))
return BadRequest("Invalid search query");
ViewBag.Query = query;
return View();
}

Step 4: Implementing Content Security Policy (CSP)

Add a CSP header to restrict script sources, mitigating XSS risks.

In Program.cs:

csharp
app.Use(async (context, next) =>
{
context.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; script-src 'self';");
await next();
});

Explanation: This CSP allows scripts only from the same origin ('self'), blocking external or inline scripts.

Interactive Challenge: Add a CSP that allows scripts from a trusted CDN (e.g., https://cdn.jsdelivr.net). Test with an inline script—does it run?

Best Practice: Use strict CSP policies in production. Test thoroughly to avoid blocking legitimate scripts.

Section 3: Preventing CSRF in ASP.NET Core MVC

Step 1: Enable CSRF Protection

ASP.NET Core MVC provides built-in CSRF protection using anti-forgery tokens. Tokens are generated per form and validated on submission.

Create a form in Views/Tasks/Create.cshtml:

html
@model TaskItem
<form asp-action="Create" method="post">
<input asp-for="Title" />
<textarea asp-for="Description"></textarea>
<input type="submit" value="Create Task" />
</form>

Add the controller action:

csharp
[HttpGet]
public IActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken] // Validates CSRF token
public async Task<IActionResult> Create(TaskItem task)
{
if (!ModelState.IsValid) return View(task);
_context.Tasks.Add(task);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}

Explanation: The asp-action tag helper automatically includes an anti-forgery token in the form. [ValidateAntiForgeryToken] ensures the token is valid on POST.

Interactive Challenge: Remove [ValidateAntiForgeryToken] and submit the form via a curl request without a token. What error occurs?

Step 2: CSRF for AJAX Requests

For AJAX POSTs (e.g., in a SPA), include the anti-forgery token manually.

View with AJAX:

html
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Antiforgery
<form id="taskForm">
<input asp-for="Title" />
<textarea asp-for="Description"></textarea>
<button type="submit">Create Task</button>
</form>
@section Scripts {
<script>
const token = '@Antiforgery.GetAndStoreTokens(Context).RequestToken';
document.getElementById('taskForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
await fetch('/Tasks/CreateAjax', {
method: 'POST',
headers: {
'RequestVerificationToken': token,
'Content-Type': 'application/json'
},
body: JSON.stringify({
Title: formData.get('Title'),
Description: formData.get('Description')
})
});
});
</script>
}

Controller Action:

csharp
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CreateAjax([FromBody] TaskItem task)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
_context.Tasks.Add(task);
await _context.SaveChangesAsync();
return Ok("Task created");
}

Best Practice: Always include the CSRF token in AJAX headers (RequestVerificationToken).

Step 3: Secure Cookies with SameSite

Set cookies to SameSite=Strict or SameSite=Lax to reduce CSRF risks.

In Program.cs:

csharp
builder.Services.AddSession(options =>
{
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
app.UseSession();

Explanation: SameSite=Strict prevents cookies from being sent in cross-site requests, mitigating CSRF.

Section 4: Advanced Scenarios

Scenario 1: Input Validation for XSS

Validate and sanitize user input to prevent XSS. Use libraries like HtmlSanitizer.

Install:

bash
dotnet add package Ganss.XSS

Sanitize in the controller:

csharp
using Ganss.XSS;
public async Task<IActionResult> Create(TaskItem task)
{
var sanitizer = new HtmlSanitizer();
task.Description = sanitizer.Sanitize(task.Description); // Remove malicious HTML
if (!ModelState.IsValid) return View(task);
_context.Tasks.Add(task);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}

Pros: Removes dangerous tags while allowing safe HTML. Cons: Adds processing overhead. Best Practice: Combine sanitization with Razor encoding for defense-in-depth.

Interactive Challenge: Submit a description with <b>Bold</b><script>alert('XSS')</script>. What does the sanitized output look like?

Scenario 2: Multi-Tenant CSRF Tokens

In a multi-tenant app, ensure CSRF tokens are tenant-specific.

Add tenant ID to tokens:

csharp
builder.Services.AddAntiforgery(options =>
{
options.Cookie.Name = "X-CSRF-TOKEN-{tenantId}"; // Dynamic per tenant
});

Pros: Isolates tokens per tenant. Cons: Requires tenant ID management. Best Practice: Use a middleware to set the tenant ID dynamically.

Section 5: Pros, Cons, Alternatives, Best Practices, and Standards

Overall Pros of XSS and CSRF Prevention:

  • Security: Protects users from data theft and unauthorized actions.
  • Compliance: Aligns with OWASP, GDPR, and PCI-DSS requirements.
  • User Trust: Enhances app reliability and reputation.

Overall Cons:

  • Complexity: Adds validation and token management overhead.
  • Performance: Encoding, sanitization, and token validation introduce latency.
  • Maintenance: Requires regular updates to address evolving threats.

Alternatives:

  • Headless CMS: Offload content management to reduce XSS risks.
  • Token-Based Auth (JWT): Eliminates CSRF for APIs, but requires careful token management.
  • Serverless: Use platforms like Azure Functions with built-in security.

Best Practices:

  • XSS: Encode all outputs with Razor; avoid Html.Raw unless sanitized.
  • CSRF: Use [ValidateAntiForgeryToken] for all POST actions; include tokens in AJAX.
  • Cookies: Set SameSite=Strict and Secure flags.
  • Validation: Sanitize inputs with libraries like HtmlSanitizer.
  • Testing: Use tools like OWASP ZAP or Burp Suite to scan for vulnerabilities.

Standards:

  • Follow OWASP Top 10 for XSS and CSRF mitigation.
  • Adhere to W3C CSP specifications for script control.
  • Use NIST SP 800-63B for secure authentication practices.

Section 6: Troubleshooting Common Issues

Issue 1: XSS Script Executes

Symptom: Malicious scripts run in the browser. Solution: Avoid Html.Raw; ensure Razor encoding is active. Check CSP headers.

Issue 2: CSRF Token Validation Fails

Symptom: 403 Forbidden on form submission. Solution: Verify the token is included in forms (asp-action) or AJAX headers. Check cookie settings (SameSite, Secure).

Debugging Tips:

  • Use browser dev tools to inspect rendered HTML and headers.
  • Enable EF Core logging for database-related issues:

csharp
builder.Services.AddDbContext<TaskContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))
.EnableSensitiveDataLogging()); // Dev only

  • Test with curl or Postman to simulate CSRF attacks.

Section 7: Complete Example

Here’s a secure task management MVC app with XSS and CSRF protection.

csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Ganss.XSS;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TaskContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddControllersWithViews();
builder.Services.AddSession(options =>
{
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
var app = builder.Build();
app.UseStaticFiles();
app.UseSession();
app.Use(async (context, next) =>
{
context.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; script-src 'self';");
await next();
});
app.UseRouting();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Tasks}/{action=Index}/{id?}");
app.Run();
public class TaskItem
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public bool IsCompleted { get; set; }
}
public class TaskContext : DbContext
{
public TaskContext(DbContextOptions<TaskContext> options) : base(options) { }
public DbSet<TaskItem> Tasks { get; set; }
}
public class TasksController : Controller
{
private readonly TaskContext _context;
private readonly HtmlSanitizer _sanitizer;
public TasksController(TaskContext context)
{
_context = context;
_sanitizer = new HtmlSanitizer();
}
public IActionResult Index()
{
var tasks = _context.Tasks.ToList();
return View(tasks);
}
[HttpGet]
public IActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(TaskItem task)
{
task.Description = _sanitizer.Sanitize(task.Description); // Sanitize input
if (!ModelState.IsValid) return View(task);
_context.Tasks.Add(task);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
}

Views/Tasks/Index.cshtml:

html
@model IEnumerable<TaskItem>
<h1>Tasks</h1>
@foreach (var task in Model)
{
<div>@task.Title - @task.Description</div> <!-- Auto-encoded -->
}
<a asp-action="Create">Create New Task</a>

Views/Tasks/Create.cshtml:

html
@model TaskItem
<form asp-action="Create" method="post">
<input asp-for="Title" />
<textarea asp-for="Description"></textarea>
<input type="submit" value="Create Task" />
</form>

Interactive Challenge: Submit a malicious description like <script>alert('XSS');</script>. Verify it’s sanitized and doesn’t execute. Try a forged POST request without a CSRF token—what happens?

Conclusion: Building Secure ASP.NET Core MVC Apps

Preventing XSS and CSRF attacks in ASP.NET Core MVC is essential for protecting user data and maintaining trust. By leveraging Razor’s encoding, anti-forgery tokens, CSP, and input sanitization, you can secure your task management app against common threats. Follow best practices—encode outputs, validate tokens, and test regularly—to stay ahead of attackers.

Try the code in your project! Experiment with CSP policies or sanitization rules. Share your security tips or questions in the comments. What’s your next secure coding challenge?

No comments:

Post a Comment

Thanks for your valuable comment...........
Md. Mominul Islam

Post Bottom Ad

Responsive Ads Here