Ways Developer Can Secure An ASP.NET Application (Redux 2022)
Updating my post back in 2015 here for year 2022.
Cross-Site Scripting (XSS)
-
According to OWASP’s definition of XSS, XSS is a type of injection that occurs when a web application uses input from a user within the output it generates without validating or encoding it
-
To avoid XSS in ASP.NET, use Razor’s expression syntax with the
@
symbol preceeding variable names, e.g.@InputString
, as this will escape HTML characters in the input string by defaultFor example the following code:
@("<span>Hello World</span>")
will render the following HTML:
<span>Hello World</span>
-
Use the HttpUtility.HtmlEncode() function inside your Razor code:
<%= HttpUtility.HtmlEncode(UserInput) %>
-
Avoid using HtmlHelper.Raw(), unless you validate and sanitize the input string variable
Same Origin Policy and Cross Origin Resource Sharing (CORS)
-
According to MDN’s definition of same origin policy, same origin policy restricts how a document or script loaded by one origin can interact with a resource from another origin
-
2 URLs have different origin if any of the following is different:
- protocol (
http
vshttps
) - domain (
https://my-site.com
vshttps://www.my-site.com
) - port (
:443
vs:5000
)
- protocol (
-
CORS on the other hand, according to OWASP’s definition of CORS, allows a web application to expose resources to all or restricted domain and allows a web client to make AJAX request for resource on other domain than its source domain
-
The process involves the browser sending the Origin HTTP request header (with origin URL as its value) for cross domain request to the server. The server then sending the Access-Control-Allow-Origin HTTP response header (with origin URL as its value) to allow CORS. Then lastly the browser checking if URL in
Access-Control-Allow-Origin
header matches the origin URL. -
To enable CORS per endpoint in ASP.NET:
- Use [EnableCors] attribute at the controller level
- And call CorsHttpConfigurationExtensions.EnableCors() on startup/configuration
-
You can enable CORS in
web.config
using <customHeaders> but this will apply for all requests:<system.webServer> <httpProtocol> <customHeaders> <add name="Access-Control-Allow-Origin" value="https://..." /> </customHeaders> </httpProtocol> </system.webServer>
SQL Injection
-
Avoid concatenating user input to SQL query statements
-
Use parameterized query or prepared statements
In your SQL query use SQL variables:
SELECT * FROM product WHERE id = @id
And in your ASP.NET code, use SQL command parameters:
command.Parameters.AddWithValue("@id", id);
-
Additionally validate user input
-
Avoid execution of raw SQL even in Entity Framework 6 (EF6) - EF6 calls such as as Database.SqlQuery() and Database.ExecuteSqlCommand()
-
For Entity Framework Core (EF Core), see Passing parameters in EF Core
-
Also, avoid returning IQueryable in EF and instead call ToList() before returning results
Cross-Site Request Forgery (CSRF)
-
According to OWASP’s definition of CSRF, CSRF forces an end user to execute unwanted actions on a web application in which they are currently authenticated.
-
An example of CSRF is luring a user to visit the attacker’s site where the attacker is able to send an authenticated HTTP request to the server being attacked that the user has previously logged in
-
To prevent CSRF in ASP.NET, antiforgery tokens are used and they work because the malicious page cannot read the user’s tokens, due to same-origin policies
Use HtmlHelper.AntiForgeryToken() to add antiforgery token to a
<form>
element:@using (Html.BeginForm("DeleteProduct", "Admin")) { @Html.AntiForgeryToken() ... }
And add the [ValidateAntiForgeryToken] attribute to the controller action:
[HttpPost] [ValidateAntiForgeryToken] public ActionResult DeleteProduct(...) { ... }
-
For ASP.NET Core, see Prevent Cross-Site Request Forgery (XSRF/CSRF) attacks in ASP.NET Core
Web.config
-
ASP.NET or specifically IIS by default forbids downloading the
web.config
file via HTTP as this is configured in it’s config under <system.web>/<httpHandlers>:<system.web> <httpHandlers> <add verb="*" path="*.config" type="System.Web.HttpForbiddenHandler" /> </httpHandlers> </system.web>
-
But still it should not contain sensitive information as this
web.config
file can be stored in the project’s source control repository -
You can store your app settings and connections strings in an external file which you can refer to in your
web.config
(see <appSettings> file attribute and <connectionStrings> configSource attribute):
<appSettings file="secrets.appSettings.config">
...
</appSettings>
<connectionStrings configSource="secrets.connectionStrings.config">
</connectionStrings>
-
Encrypt sections of your
web.config
file (see Encrypting and Decrypting Configuration Sections) -
If you are using a cloud provider, use the Key Vault in Azure or Secrets Manager in AWS to store sensitive information
Password Hashing
-
You can use Rfc2898DeriveBytes to hash password
-
Or use ASP.NET Core Identity PasswordHasher (see Exploring the ASP.NET Core Identity PasswordHasher)
Cookies
- Secure cookies in ASP.NET code (see HttpCookie Class) by setting the following properties:
-
Secure - set to
true
to only transmit cookie using SSL, that is over HTTPS -
HttpOnly - set to
true
to prevent client-side script from accessing the cookie -
SameSite - set to
strict
to prevent cookie from being sent in all cross-site browsing contexts or set tolax
for a more balanced approach between security and usability
cookie = new HttpCookie("NewCookie"); cookie.Secure = true; cookie.HttpOnly = true; cookie.SameSite = SameSiteMode.Strict // or SameSiteMode.Lax
-
Secure - set to
Sessions
-
Secure sessions by setting properties in
web.config
under<system.web>/<sessionState>
(see SessionStateSection Class) and<system.web>/<httpCookies>
(see HttpCookiesSection Class):<system.web> <sessionState cookieless="false" regenerateExpiredSessionId="false" timeout="20" /> <httpCookies httpOnlyCookies="true" requireSSL="true" sameSite="Strict" /> </system.web>
-
Setting
<sessionState> cookieless
attribute tofalse
will prevent encoding the session ID in the URL which is prone to a security attack and instead use a cookie to store the session ID -
Setting
<sessionState> regenerateExpiredSessionId
attribute tofalse
will prevent using the same session ID value when expired -
Setting a
<sessionState> timeout
is recommended to prevent a session running too long which make it more vulnerable to security attack
Enforce HTTPS
-
Enforce HTTPS in ASP.NET code by redirecting after checking HttpRequest.IsSecureConnection:
if (!HttpContext.Current.Request.IsSecureConnection) { Response.Redirect("https://" + Request.ServerVariables["HTTP_HOST"] + HttpContext.Current.Request.RawUrl); }
-
You can add a rewrite rule in
web.config
:<system.webServer> <rewrite> <rules> <rule name="HTTP to HTTPS redirect" stopProcessing="true"> <match url="(.*)" /> <conditions> <add input="{HTTPS}" pattern="off" ignoreCase="true" /> </conditions> <action type="Redirect" redirectType="Permanent" url="https://{HTTP_HOST}/{R:1}" /> </rule> </rules> </rewrite> </system.webServer>
-
Or you can add the HTTP Strict-Transport-Security (HSTS) header to the response
-
For ASP.NET Core, see Enforce HTTPS in ASP.NET Core for information on
UseHttpsRedirection()
andUseHsts()
Error handling
-
Having a custom error page allows you to tailor your error messages from displaying too much information or from displaying sensitive information that can lead to a security attack
-
In ASP.NET, you can add custom error handler in
Global.asax
file underApplication_Error()
:protected void Application_Error(object sender, EventArgs e) { Exception exception = Server.GetLastError(); System.Diagnostics.Debug.WriteLine(exception); Response.Redirect("/Home/Error"); }
-
You can configure custom error messages/pages in
web.config
For example you can add this to your
web.config
file:<customErrors mode="On"> <error statusCode="404" redirect="~/Home/MyCustomErrorPage" /> </customErrors>
Then in your
HomeController
code, you would have this method defined:public ActionResult MyCustomErrorPage(...) { ... }
And of course the corresponding view file
MyCustomErrorPage.cshtml
should have been created as well