Application security
.NET Core Best Practices C# OWASP Security SQL

Applying OWASP Principles in a .NET Core Application

Welcome to today’s post.

Today I will show how to use the OWASP in your .NET Core application.

In many commercial applications that are publicly facing, there is a need to practice sound security principles that govern the design, implementation, and deployment of the system. Without these principles being applied at each phase of development and maintenance, the system will have security loopholes that allow external attacks to occur on the system, regardless of whether you have implemented additional access levels for application users. I will go through some of the OWASP principles that are applied to application implementations that make applications more secure.  

I will start by defining with OWASP is.

What is OWASP?

OWASP is The Open Web Application Security Project® foundation provides a set of guidelines that helps developers improve the security of software.

These guidelines cover many aspects of software security. These include:

  1. Scripting security.
  2. Data security.
  3. Access principles.
  4. Password policies.
  5. Encryption strength.
  6. Access monitoring and auditing.

Some of these principles you may already be aware of and some you may not be. With the above guidelines I will cover some examples that related to them in .NET Core and EF Core.  

SQL Injection Attacks

The recommendation is to use an ORM like Entity Framework Core or Stored Procedure Parametrisation.

With EF Core, the use of the extension method FromSql() is not implemented with care can expose your application to SQL injection attacks.

Using LINQ to Entities to query data from our database is safer than using raw SQL queries.

For example, the following LINQ to Entities query:

var book = await _db.Books.Where(m => m.ID == id).SingleOrDefaultAsync();

Will be parametrized by EF Core query processor before it is dispatched to SQL Server.

Any arguments used within the query would be escaped so they are not included as valid SQL within the resulting query.

If you used a dynamic SQL query that contained concatenated string parameters, then this would be very risky as any string parameters, without proper sanitization for dangerous commands and escaping would cause quite dangerous queries to be run on the backend database server, potentially removing or scrambling critical data.

Least Privilege Access

Where applicable, use database accounts that have the least privileges to achieve their goals.

If you are developing a reporting API, then the account used to access the API methods should have read only access (db_datareader).

If you are developing an API that processes transaction, then the account used to access the API methods should have read and write access (db_datareader, db_datawriter).

With a Web API service, ideally the account used in the database connection should vary based on database privileges.

Never use the sa account or an account that has admin privileges.

Sensitive Data Exposure

Password generation has in the past decade gone from using encryption-based technologies, which can be decrypted, to adopting a safer hash-based password generation. As we know, encryption-based password generation is not as safe as an attacker who obtains the private key can compromise all passwords in the database.

With a hash-based password the algorithm is unidirectional: we cannot decrypt the hashed password. With hashed passwords we can only run a dictionary attack which compares many combinations of a password to the stored hash password. With a salt added to the password, a dictionary attack becomes impractical.

We should use a strong enough hashing algorithm such as HmacSha256, which will adequately scramble our password and render attacks impractical. An additional salt will increase the strength of the hash password further. For example, in .NET Core we can set the signing credentials as shown:

byte[] key = Convert.FromBase64String(_appConfiguration.Secret);
SigningCredentials = new SigningCredentials(
    new SymmetricSecurityKey(key),
    SecurityAlgorithms.HmacSha256Signature)

In addition, every hashed password generated per user account is automatically randomly hashed before storage.

Adequate Password Policies

In addition to a strong hashing algorithm, the enforcement of strong password policies will allow the hashed passwords to be as strong as possible when faced with dictionary attacks.

By enforcing minimum lengths and variety of characters we can ensure that stored hashes if compromised are extremely difficult to attack.

In .NET Core Identity, we can configure the password policies are shown:

services.Configure<IdentityOptions>(options =>
{
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
    options.Lockout.MaxFailedAccessAttempts = 3;
    options.SignIn.RequireConfirmedEmail = true;
    options.User.RequireUniqueEmail = true;
});

In addition, on login attempts and lockouts ensure that frequent failed attempts to login are trapped, logged and the affected accounts locked out.

Lockout policies can be enforced as shown:

services.Configure<IdentityOptions>(options =>
{
    options.Password.RequireDigit = true;
    options.Password.RequiredLength = 8;
    options.Password.RequireNonAlphanumeric = true;
    options.Password.RequireUppercase = true;
    options.Password.RequireLowercase = true;
    options.Password.RequiredUniqueChars = 6;
});

Broken Access Control

Reducing the time spend during a session will reduce the chances of an attacker hijacking the session and accessing important user information.

With JWT tokens we can set an expiry for the token so that if the user attempts to access an API method using an expired token, then a 401 exception will occur and the user will be challenged to login again.

SymmetricSecurityKey securityKey = new SymmetricSecurityKey(key);
       
SecurityTokenDescriptor descriptor = new SecurityTokenDescriptor
{
    Subject = new ClaimsIdentity(new[] {
        new Claim(ClaimTypes.Name, username)}),
        Expires = DateTime.Now.AddMinutes(30),
        SigningCredentials = new SigningCredentials(
            new SymmetricSecurityKey(key),
            SecurityAlgorithms.HmacSha256Signature)
    };

When authenticating users, you should not tell the end user that the password for that username is invalid. This would allow attackers to enumerate accounts that are valid and return with further attacks. Code that does the following is bad practice:

bool isAuthenticated = _userService.Authenticate(userDto.UserName, userDto.Password);
if (!isAuthenticated)
    return BadRequest(new { message = "The password is incorrect" });

The preferred message would be: “Username or password is incorrect”

Weak Method Access Control

Another security vulnerability is ensuring API methods that return sensitive data or modify sensitive backend data are protected.

By using appropriate authorization, we can prevent unauthorized users from accessing our methods.

We can either use an imperative based security check or declarative security check. Below is an example of declarative security using JWT token security for our API method:

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[HttpGet("api/[controller]/{id}")]
public IActionResult GetById(string id)
{
    var user =  _userService.GetById(id);
    UserViewModel userView = new UserViewModel()
    {
        Id = user.Id,
        UserName = user.UserName,
        Email = user.Email,
        FirstName = user.FirstName,
        LastName = user.LastName,
        DOB = user.DOB
    };
    return Ok(userView);
}

Insecure Direct Object Access

By allowing an API method to access data using an identifier we are opening-up the possibility that an end user can access sensitive data or unintended data. By protecting the code block by using imperative or declarative security we can ensure that only authorized users can access the data or method.

An example of imperative security is show below where we restrict access within the method based on the role of the user:

[HttpGet("api/[controller]/Details/{id}")]
public async Task<ActionResult> Details(int id)
{
    if (id == 0)
    {
        return NotFound(new { id });
    }
    try
    {
  	    if ((id == SENSITIVE_BOOK_ID) && (!HttpContext.User.IsInRole("Manager"))
            throw new Exception("Not permitted to view book");

        BookStatusViewModel bvm = new BookStatusViewModel();
        var book = await _db.Books.Where(a => a.ID == id).SingleOrDefaultAsync();
        return Ok(bvm);
    }
    catch (Exception ex)
    {
        return BadRequest( new { ex.Message });
    }
}

Insufficient Logging and Monitoring

For any web application to be able to be diagnosed for potential security issues that cause exceptions we will need to log the errors so that we can diagnose what the core problem is. The error can be logged to stdout which is then written to a log file. 

There are a few guidelines we should take while we implement logging:

  1. Ensure that the logging is not generic. A log message such as “There was a user error.” Is not informative enough to lead support staff to the root cause of the problem.
  2. Do not log sensitive data such as passwords, or user details such as dates of birth. These can be used to compromise security if server access is breached.
  3. Do log the full exception stack trace. This will provide the most detailed information when diagnosing issues without giving sensitive details away.

In a.NET Core application we can use the provided logging provider:

Microsoft.Extensions.Logging

In our startup.cs, we can enable logging for production with:

if (!env.IsDevelopment())
{
    app.UseExceptionHandler(errorApp =>
    {
        errorApp.Run(async context =>
        {
            var errorFeature = context.Features.Get<IExceptionHandlerFeature>();
            var exception = errorFeature.Error;

            logger.LogError(String.Format("Stacktrace of error: {0}", 
                exception.StackTrace.ToString()));
        });
    });
 }

Then we can use Dependency Injection to make out logger available within any controller or service in our application:

public class BookController : Controller
{
    ApplicationDbContext _db;
    IBookService _bookService;
    private readonly ILogger _logger;

    public BookController(ApplicationDbContext db,
        ILogger<BookController> logger,
        IBookService bookService)
    {
        _db = db;
        _logger = logger;
        _bookService = bookService;
    }

    public async Task<ActionResult> Create([FromBody] BookViewModel model)
    {
        try
        {
           …
        }
        catch (Exception ex)
        {
            _logger.LogError(ex.Message);
            return BadRequest("Cannot create book record.");
        }
    }

As we can see, there are well known important security principles and best practices we can apply within out .NET Core applications to tighten up the overall security and make them compliant when exposed as public facing services or APIs.

That’s all for today’s post.

There are a few areas of OWASP I have not covered, and I will provide a further update post in this area in future.

I hope this has given you some ideas on securing your application.

Social media & sharing icons powered by UltimatelySocial