When talking about applications which are developed in .NET or .NET Core, we should follow some standards or rules which should not break the security vulnerability which is defined by OWASP TOP 10 – the most critical security risks to web applications.
The detailed description of OWASP top 10 security risks are found here (https://owasp.org/www-project-top-ten/). In this blog, the only mentioned points are for to do and don’t do while developing .net or .net core application.
SQL Injection
SQL Injection works by modifying an input parameter that is known to be passed into a raw SQL statement, in a way that the SQL statement executed is very different to what is intended.
- Use an object relational mapper (ORM e.g. Entity Framework) or stored procedures is the most effective way of countering the SQL Injection vulnerability.
- Use parameterized queries where a direct sql query must be used e.g. In entity frameworks:
var sql = @"Update [User] SET FirstName = @FirstName WHERE Id = @Id"; context.Database.ExecuteSqlCommand( sql, new SqlParameter("@FirstName", firstname), new SqlParameter("@Id", id));
- Do not concatenate strings anywhere in your code and execute them against your database (Known as dynamic sql).
NB: You can still accidentally do this with ORMs or Stored procedures so check everywhere.
e.g.
string strQry = "SELECT * FROM Users WHERE UserName='" + userNameVar + "' AND Password='" + userPasswordVar + "'"; EXEC strQry // Here is the SQL Injection vulnerability, in case like suppose if user pass the value like xxx’ OR 1 = 1
- Practice Least Privilege – Connect to the database using an account with a minimum set of permissions required to do it’s job (e.g. do not allow to each developer or application to access with sa account, instead create a separate user with specific permission and use it).
Broken Authentication
Using this vulnerability, an attacker can gain control over user accounts in a system. In the worst case, it could help them gain complete control over the system.
- Enable multi factor authentication.
- Use ASP.net Core Identity. ASP.net Core Identity framework is well configured by default, where it uses secure password hashes and an individual salt. Identity uses the PBKDF2 hashing function for passwords, and they generate a random salt per user.
- Prevent weak and default password policy, use of the below password policy can make the password strong.
e.g.
ASP.net Core Identity
//startup.cs services.Configure<IdentityOptions>(options => { // Password settings options.Password.RequireDigit = true; options.Password.RequiredLength = 8; options.Password.RequireNonAlphanumeric = true; options.Password.RequireUppercase = true; options.Password.RequireLowercase = true; options.Password.RequiredUniqueChars = 6; options.Lockout.MaxFailedAccessAttempts = 3; options.SignIn.RequireConfirmedEmail = true; options.User.RequireUniqueEmail = true; });
- Set proper cookie expiration policy.
e.g.
//startup.cs services.ConfigureApplicationCookie(options => { options.Cookie.HttpOnly = true; options.Cookie.Expiration = TimeSpan.FromHours(1) options.SlidingExpiration = true; });
- Implement strong credential recovery process.
- Limit failed login attempts.
- Implement the captcha, it will help to distinguish between human and computers action.
Sensitive Data Exposure
Sensitive data exposure occurs when an application accidentally exposes sensitive data.
It usually occurs when we fail to adequately protect the information in the database.
Various causes that can lead to this are missing or weak encryption, software flaws, storing data in the wrong place, etc.
- Do not Store encrypted passwords at client side or any other untrusted stores.
- Use a strong hash to store password credentials.
- Enforce passwords with a minimum complexity that will survive a dictionary attack i.e. longer passwords that use the full character set (numbers, symbols and letters) to increase the entropy.
- Use a strong encryption routine such as AES-512 where personally identifiable data needs to be restored to it’s original format. Protect encryption keys more than any other asset. Apply the following test: Would you be happy leaving the data on a spreadsheet on a bus for everyone to read. Assume the attacker can get direct access to your database and protect it accordingly.
- Use TLS 1.2 or greater for your entire site. For more information, please see the best practises for SSL and TLS deployment here.
- Do not use of SSL – versions 2 and 3. Both of these have serious cryptographic weaknesses and should no longer be used.
- Have a strong TLS policy (see SSL Best Practices), use TLS 1.2 wherever possible. Then check the configuration using SSL Test or TestSSL.
- Disable the caching of pages which contains sensitive information.
- Ensure headers are not disclosing information about your application. See HttpHeaders.cs , Dionach StripHeaders, disable via web.config or startup.cs:
More information on Transport Layer Protection can be found here. e.g. Web.config
e.g. Startup.cs
app.UseHsts(hsts => hsts.MaxAge(365).IncludeSubdomains()); app.UseXContentTypeOptions(); app.UseReferrerPolicy(opts => opts.NoReferrer()); app.UseXXssProtection(options => options.FilterDisabled()); app.UseXfo(options => options.Deny()); app.UseCsp(opts => opts .BlockAllMixedContent() .StyleSources(s => s.Self()) .StyleSources(s => s.UnsafeInline()) .FontSources(s => s.Self()) .FormActions(s => s.Self()) .FrameAncestors(s => s.Self()) .ImageSources(s => s.Self()) .ScriptSources(s => s.Self()) );
Broken Access Control
Broken Access Control refers to the ability for an end user, whether through tampering of a URL, cookie, token, or contents of a page, to essentially access data that they shouldn’t have access to.
For example, following points defined as Broken Access Control by OWASP.
- Misconfigured or too broad CORS configuration
- Web server directory listing/browsing
- Backups/Source control (.git/.svn) files present in web roots
- Rate limiting of APIs
- JWT Tokens not being invalidated on logout
Weak Account management
- Reduce the time period a session can be stolen in by reducing session timeout and removing sliding expiration:
ExpireTimeSpan = TimeSpan.FromMinutes(60), SlidingExpiration = false
- Ensure cookie is sent over HTTPS in the production environment. This should be enforced in the config transforms:
- Protect LogOn, Registration and password reset methods against brute force attacks by throttling requests (see code below), consider also using ReCaptcha.
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] [AllowXRequestsEveryXSecondsAttribute(Name = "LogOn", Message = "You have performed this action more than {x} times in the last {n} seconds.", Requests = 3, Seconds = 60)] public async Task LogOn(LogOnViewModel model, string returnUrl)
- Tell someone if the account exists on LogOn, Registration or Password reset. Say something like ‘Either the username or password was incorrect’, or ‘If this account exists then a reset token will be sent to the registered email address’. This protects against account enumeration.
Missing function-level access control
- Authorize users on all externally facing endpoints. The .NET framework has many ways to authorize a user, use them at method level:
[Authorize(Roles = "Admin")] public ActionResult Index() or better yet, at controller level: [Authorize] public class UserController
- You can also check roles in code using identity features in .net: System.Web.Security.Roles.IsUserInRole(userName, roleName)
Insecure Direct object references
- Make sure the resource access by authenticated user and you need to ensure that the user is intended to be there.
// Insecure public ActionResult Edit(int id) { var user = _context.Users.FirstOrDefault(e => e.Id == id); return View("Details", new UserViewModel(user); } // Secure public ActionResult Edit(int id) { var user = _context.Users.FirstOrDefault(e => e.Id == id); // Establish user has right to edit the details if (user.Id != _userIdentity.GetUserId()) { HandleErrorInfo error = new HandleErrorInfo( new Exception("INFO: You do not have permission to edit these details")); return View("Error", error); } return View("Edit", new UserViewModel(user); }
Security Misconfiguration
Improper server or web application configuration will lead to security vulnerability to various flows.
Debug and Stack Trace
- Ensure debug and trace are off in production. This can be enforced using web.config transforms:
- Do not use the default passwords for any of the accounts and configuration settings.
- Incorrect folder permission on web server.
- Consider running scans and doing audits periodically to help detect future misconfigurations or missing patches.
- When using TLS, redirect a request made over Http to https:
e.g. Global.asax.cs (ASP.NET)
protected void Application_BeginRequest() { #if !DEBUG // SECURE: Ensure any request is returned over SSL/TLS in production if (!Request.IsLocal && !Context.Request.IsSecureConnection) { var redirect = Context.Request.Url.ToString() .ToLower(CultureInfo.CurrentCulture) .Replace("http:", "https:"); Response.Redirect(redirect); } #endif }
OR
Write a rule to redirect rule from http to https in IIS
e.g. Startup.cs in the Configure() (in .NET Core)
app.UseHttpsRedirection();
Cross-site request forgery
- Do not send sensitive data without validating Anti-Forgery-Tokens (.NET / .NET Core).
- Send the anti-forgery token with every POST/PUT request:
USING .NET FRAMEWORK
using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", @class = "pull-right" })) { @Html.AntiForgeryToken()
-
- Logged on as @User.Identity.Name
}
Then validate it at the method or preferably the controller level:
[HttpPost] [ValidateAntiForgeryToken] public ActionResult LogOff()
-
- Make sure the tokens are removed completely for invalidation on logout.
// SECURE: Remove any remaining cookies including Anti-CSRF cookie public void RemoveAntiForgeryCookie(Controller controller) { string[] allCookies = controller.Request.Cookies.AllKeys; foreach (string cookie in allCookies) { if (controller.Response.Cookies[cookie] != null && cookie == "__RequestVerificationToken") { controller.Response.Cookies[cookie].Expires = DateTime.Now.AddDays(-1); } } }
USING .NET CORE 2.0 OR LATER
Starting with .NET Core 2.0 it is possible to automatically generate and verify the antiforgery token.
- If you are using tag-helpers, which is the default for most web project templates, then all forms will automatically send the anti-forgery token. You can check if tag-helpers are enabled by checking if your main _ViewImports.cshtml file contains:
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
IHtmlHelper.BeginForm also sends anti-forgery-tokens automatically.
Unless you are using tag-helpers or IHtmlHelper.BeginForm, you must use the requisite helper on forms as seen here:
- To automatically validate all requests other than GET, HEAD, OPTIONS and TRACE you need to add a global action filter with the AutoValidateAntiforgeryToken attribute inside your Startup.cs as mentioned in the following article:
services.AddMvc(options => { options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); });
- If you need to disable the attribute validation for a specific method on a controller you can add the IgnoreAntiforgeryToken attribute to the controller method (for MVC controllers) or parent class (for Razor pages):
[IgnoreAntiforgeryToken] public IActionResult MyAction()
USING .NET CORE OR .NET FRAMEWORK WITH AJAX
You will need to attach the anti-forgery token to AJAX requests.
If you are using jQuery in an ASP.NET Core MVC view this can be achieved using this snippet:
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery antiforgeryProvider $.ajax( { type: "POST", url: '@Url.Action("Action", "Controller")', contentType: "application/x-www-form-urlencoded; charset=utf-8", data: { id: id, '__RequestVerificationToken': '@antiforgeryProvider.GetAndStoreTokens(this.Context).RequestToken' } })
If you are using the .NET Framework, you can find some code snippets here.
Cross-Site Scripting (XSS)
Cross Site Scripting attack (XSS) is a type of injection, in which malicious scripts are injected into trusted websites. XSS attacks occur when an attacker uses a web application to send malicious code, generally in the form of a browser side script
- Do not trust any data the user sends you, prefer allow lists (always safe) over block lists
- You get encoding of all HTML content with MVC3, to properly encode all content whether HTML, javascript, CSS, LDAP etc use the Microsoft AntiXSS library:
Install-Package AntiXSS
Then set in config:
- Use the [AllowHTML] attribute or helper class @Html.Raw unless you really know that the content you are writing to the browser is safe and has been escaped properly.
- Enable a Content Security Policy, this will prevent your pages from accessing assets it should not be able to access (e.g. a malicious script):
More information can be found here for Cross-Site Scripting.
Insecure Deserialization
Parameter tampering is a form of Web-based attack in which certain parameters in the Uniform Resource Locator (URL) or Web page form field data entered by a user are changed without that user’s authorization.
- Do not accept Serialized Objects from Untrusted Sources. Validate it before use.
- Validate User Input Malicious users are able to use objects like cookies to insert malicious information to change user roles. In some cases, hackers are able to elevate their privileges to administrator rights by using a pre-existing or cached password hash from a previous session.
- Prevent Deserialization of Domain Objects.
- Run the Deserialization Code with Limited Access Permissions If a deserialized hostile object tries to initiate a system processes or access a resource within the server or the host’s OS, it will be denied access and a permission flag will be raised so that a system administrator is made aware of any anomalous activity on the server.
More information can be found here: Deserialization Cheat Sheet
Using Components with Known Vulnerabilities
Virtually every application has these issues because most development teams don’t focus on ensuring their components/libraries are up to date. In many cases, the developers don’t even know all the components they are using, never mind their versions. Component dependencies make things even worse.
- Keep the .Net framework updated with the latest patches.
- Keep your NuGet packages up to date, many will contain their own vulnerabilities.
- Run the OWASP Dependency Checker against your application as part of your build process and act on any high level vulnerabilities.
Insufficient Logging & Monitoring
- Ensure all login, access control failures and server-side input validation failures can be logged with sufficient user context to identify suspicious or malicious accounts.
- Establish effective monitoring and alerting so suspicious activities are detected and responded to in a timely fashion.
- Do not log generic error messages such as: csharp Log.Error(“Error was thrown”); rather log the stack trace, error message and user ID who caused the error.
- Do not log sensitive data such as user’s passwords.
Logging
.NET Core come with a LoggerFactory, which is in Microsoft.Extensions.Logging
How to log all errors from the Startup.cs, so that anytime an error is thrown it will be logged.
//Log all errors in the application app.UseExceptionHandler(errorApp => { errorApp.Run(async context => { var errorFeature = context.Features.Get(); var exception = errorFeature.Error; Log.Error(String.Format("Stacktrace of error: {0}",exception.StackTrace.ToString())); }); });
e.g. Injecting into the class constructor, which makes writing unit test simpler. It is recommended if instances of the class will be created using dependency injection (e.g. MVC controllers). The below example shows logging of all unsuccessful log in attempts.
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task Login(LoginViewModel model) { if (ModelState.IsValid) { var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false); if (result.Succeeded) { //Log all successful log in attempts Log.Information(String.Format("User: {0}, Successfully Logged in", model.Email)); //Code for successful login } else { //Log all incorrect log in attempts Log.Information(String.Format("User: {0}, Incorrect Password", model.Email)); } } }
Monitoring
Monitoring allow us to validate the performance and health of a running system through key performance indicators.
In .NET a great option to add monitoring capabilities is Application Insights.
More information about Logging and Monitoring can be found here.
- When using a hashing function to hash non-unique inputs such as passwords, use a salt value added to the original value before hashing.
- Make sure your application or protocol can easily support a future change of cryptographic algorithms.
- Use Nuget to keep all of your packages up to date. Watch the updates on your development setup, and plan updates to your applications accordingly.
Thank you to the CheatSheets Series Team of Microsoft from which most of the content are derived for the purpose of writing secure code or application configuration to save ourselves from hacker attack.
Mr. Shailesh Sakaria