Security Hardening
|
This section documents ASP.NET MVC 5.3.x, ASP.NET Web API 2.2, ASP.NET Web Pages 3, OWIN/Katana, SignalR 2,
and ASP.NET Identity 2 — all running on .NET Framework 4.8.1 — the This is not ASP.NET Core MVC. ASP.NET MVC 5 is the pre-Core, This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
This page documents security hardening for ASP.NET MVC 5.3.x on .NET Framework 4.8.1 — not ASP.NET Core (see Security Hardening under ASP.NET Core, which covers the Data Protection API and the current antiforgery implementation).
CSRF
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Transfer(TransferViewModel model) { ... }
For AJAX POSTs, the token must be read from the DOM and sent explicitly (it is not part of a browser cookie the server can read back automatically the way ASP.NET Core’s double-submit cookie works by default):
var token = $('input[name="__RequestVerificationToken"]').val();
$.post('/Account/Transfer', { __RequestVerificationToken: token, amount: 50 });
See HTML Helpers and Forms for Html.AntiForgeryToken() and
Filters for where [ValidateAntiForgeryToken] runs as an authorization
filter.
XSS and Razor’s automatic encoding
Razor HTML-encodes every @-expression by default (see Razor Syntax);
Html.Raw is the deliberate, narrow opt-out and must never wrap unsanitized user input:
<p>@Model.Comment</p> <!-- safe: automatically encoded -->
<p>@Html.Raw(_sanitizer.Sanitize(Model.Comment))</p> <!-- only safe because it was sanitized first -->
AntiXssEncoder (Microsoft.Security.Application.AntiXss / System.Web.Security.AntiXss) was a
Microsoft-supplied, whitelist-based HTML encoder that some MVC 5 applications registered as the default
encoder (HttpUtility/AntiXssEncoder.HtmlEncode) in place of the framework’s built-in encoder, for stricter
encoding behavior; it has since been folded into `System.Web’s own encoder in later Framework versions.
Request validation and [AllowHtml]
System.Web rejects any posted value containing markup-like characters by default; [AllowHtml] narrows that
exception to one specific property, and that property must still be sanitized before ever being rendered with
Html.Raw (see Model Binding and Validation).
Open redirect
A redirect target taken directly from user input (a returnUrl query-string parameter after login, for
example) can be abused to send a victim to an attacker-controlled site while the URL still points at your own
trusted domain up to the redirect. Url.IsLocalUrl guards against this:
private ActionResult RedirectToLocal(string returnUrl)
=> Url.IsLocalUrl(returnUrl) ? (ActionResult)Redirect(returnUrl) : RedirectToAction("Index", "Home");
Clickjacking and security headers via web.config
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="X-Frame-Options" value="DENY" />
<add name="X-Content-Type-Options" value="nosniff" />
<add name="Content-Security-Policy" value="default-src 'self'" />
<add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains" />
</customHeaders>
</httpProtocol>
</system.webServer>
Unlike ASP.NET Core, where security headers are typically added via middleware in Program.cs, MVC 5’s
System.Web hosting model most commonly sets them declaratively in IIS/web.config, applying to every
response including static files IIS serves directly.
SQL injection with EF6
Parameterization is the default and correct defense: LINQ-to-Entities queries and EF6’s SqlQuery/SqlCommand
overloads that take FormattableString or explicit `SqlParameter`s are safe; string-concatenated raw SQL is not:
// Safe -- LINQ translates to a parameterized query
var product = db.Products.SingleOrDefault(p => p.Name == name);
// Safe -- parameterized raw SQL
db.Database.SqlQuery<Product>("SELECT * FROM Products WHERE Name = @p0", name);
// NEVER: string concatenation into raw SQL
// db.Database.SqlQuery<Product>("SELECT * FROM Products WHERE Name = '" + name + "'");
See Data Access with EF6 for EF6 query patterns in general.
machineKey in farms
<machineKey> derives the keys used to encrypt/validate Forms Authentication tickets, view state (where still
used), and by default the antiforgery token. In a web farm, every instance must share the same
machineKey, or a request handled by one server and validated by another (a token issued by server A, checked
by server B) fails — either intermittently invalidating antiforgery tokens or, worse, silently falling back to
per-machine keys that make CSRF tokens issued by one server unusable elsewhere:
<system.web>
<machineKey validationKey="..." decryptionKey="..." validation="SHA1" decryption="AES" />
</system.web>
[RequireHttps], HSTS, cookie secure/httpOnly
[RequireHttps] // authorization filter -- 301-redirects HTTP to HTTPS
public class AccountController : Controller { }
<system.web>
<httpCookies httpOnlyCookies="true" requireSSL="true" />
</system.web>
HSTS itself is set via the Strict-Transport-Security custom header shown above — MVC 5 has no first-class
HSTS middleware equivalent to ASP.NET Core’s UseHsts().
The OWASP Top 10 mapped onto MVC 5
| OWASP category | MVC 5 mitigation |
|---|---|
Broken Access Control |
|
Cryptographic Failures |
|
Injection |
LINQ-to-Entities / parameterized |
Insecure Design |
View models instead of binding directly to entities (see Model Binding and Validation) to avoid over-posting. |
Security Misconfiguration |
|
Vulnerable and Outdated Components |
Keeping |
Identification and Authentication Failures |
ASP.NET Identity 2’s lockout, password validators, two-factor support (see Authentication, Identity, and OWIN). |
Software and Data Integrity Failures |
Request validation, |
Security Logging and Monitoring Failures |
ELMAH, |
Server-Side Request Forgery |
Validate and allow-list any server-side outbound URL built from user input (image proxies, webhooks) — no framework-level mitigation exists. |
See ASP.NET MVC Security and the OWASP Top 10.
Next: Data Access with EF6 covers the data layer these injection defenses assume.