Chapter 11: Insufficient Logging and Monitoring

Attacks on ASP.NET Core web applications can happen at any given moment in time. Developers must empower their security teams to reconstruct an incident by generating adequate logs from web applications. Logging the right information will help determine an event's details and identify critical data for auditing purposes. The downside of failing to log key security information prevents security teams from producing proper analysis or reports. Too much logging, however, can lead to sensitive data exposure. Applying a necessary and immediate response to act on such security events is only possible through active monitoring. Developers must enable monitoring in the logs that our ASP.NET Core web applications generate for a more real-time defense.

In this chapter, we're going to cover the following recipes:

  • Fixing insufficient logging of exceptions
  • Fixing insufficient logging of database (DB) transactions
  • Fixing excessive information logging
  • Fixing a lack of security monitoring

By the end of this chapter, you will have learned how to correctly add proper exception logging in our sample Online Banking app, how to log a critical DB transaction, how to prevent logging too much data or information, and how to enable security monitoring.

Technical requirements

This book was written and designed to use with Visual Studio Code (VS Code), Git, and .NET Core 5.0. The code examples in the recipes are presented mostly in ASP.NET Core Razor Pages. The sample solution also uses SQLite as the DB engine for a more simplified setup. The complete code examples for this chapter are available at https://github.com/PacktPublishing/ASP.NET-Core-Secure-Coding-Cookbook/tree/main/Chapter11.

Fixing insufficient logging of exceptions

Security-related events such as user authentication or enabling and disabling of two-factor authentication (2FA)—when this occurs—must be recorded and kept track of. These events are essential for auditing in order to understand the sequence of events when a security incident happens.

In this recipe, we will fix the insufficient logging of security-related exceptions by utilizing ASP.NET Core's built-in logging provider.

Getting ready

For the recipes of this chapter, we will need a sample Online Banking app.

Open the command shell and download the sample Online Banking app by cloning the ASP.NET-Core-Secure-Coding-Cookbook repository, as follows:

git clone https://github.com/PacktPublishing/ASP.NET-Core-Secure-Coding-Cookbook.git

Run the sample app to verify that there are no build or compile errors. In your command shell, navigate to the sample app folder at Chapter11insufficient-logging-exceptioneforeOnlineBankingApp and run the following command:

dotnet build

The dotnet build command will build our sample OnlineBankingApp project and its dependencies.

How to do it…

Let's take a look at the steps for this recipe:

  1. From the starting exercise folder, launch VS Code by typing the following command:

    code .

  2. Open AreasIdentityPagesAccountManageDisable2fa.cshtml.cs and notice the code under OnGet:

    public async Task<IActionResult> OnGet()

    {

        var customer = await         _customerManager.GetUserAsync(User);

        if (customer== null)

        {

        return NotFound($"Unable to load customer with         ID '{_ customerManager.GetUserId(User)}'.");

        }

        if (!await _customerManager         .GetTwoFactorEnabledAsync(customer))

        {

            throw new InvalidOperationException($"Cannot             disable 2FA for customer with ID                 '{_customerManager.GetUserId(User)}'                    as it's not currently enabled.");

        }

        return Page();

    }

  3. Also, notice the code under the OnPostAsync method:

    public async Task<IActionResult> OnPostAsync()

    {

        var customer = await         _customerManager.GetUserAsync(User);

        if (customer == null)

        {

            return NotFound($"Unable to load customer with            ID '{_customerManager.GetUserId(User)}'.");

        }

        var disable2faResult = await         _customerManager.SetTwoFactorEnabledAsync             (customer, false);

        if (!disable2faResult.Succeeded)

        {

            throw new InvalidOperationException         ($"Unexpected error occurred disabling 2FA for             customer with ID '{_customerManager                 .GetUserId (User)}'.");

        }

        _logger.LogInformation("Customer with ID         '{UserId}' has disabled 2fa.",            _customerManager.GetUserId(User));

        StatusMessage = "2fa has been disabled. You can         reenable 2fa when you setup an authenticator             app";

        return         RedirectToPage("./TwoFactorAuthentication");

    }

    Both methods have a line of code where an InvalidOperationException exception is thrown. The exception indicates that an attempt to disable 2FA was made but failed. These events should be considered anomalies and logged. These events can be considered anomalies, and each should be logged.

  4. To fix a lack of event logging, we refactor the OnGet method and add logging when the InvalidOperationException exception is thrown:

    public async Task<IActionResult> OnGet()

    {

        var customer = await         _customerManager.GetUserAsync(User);

        if (customer == null)

        {

            return NotFound($"Unable to load customer with            ID '{_customerManager.GetUserId(User)}'.");

        }

        if (!await         _customerManager.GetTwoFactorEnabledAsync             (user))

        {

            _logger.LogError($"Cannot disable 2FA for             customer with ID '{_customerManager                 .GetUserId(User)}' as it's not                     currently enabled.");

            throw new InvalidOperationException($"Cannot             disable 2FA for customer with ID                 '{_customerManager.GetUserId(User)}'                    as it's not currently enabled.");

        }

        return Page();

    }

  5. We also refactor the OnPostAsync method, as shown here:

    public async Task<IActionResult> OnPostAsync()

    {

        var customer = await         _customerManager.GetUserAsync(User);

        if (customer == null)

        {

            return NotFound($"Unable to load customer with            ID '{_customerManager.GetUserId(User)}'.");

        }

        var disable2faResult = await         _customerManager.SetTwoFactorEnabledAsync             (customer, false);

        if (!disable2faResult.Succeeded)

        {

            _logger.LogError($"Unexpected error occurred             disabling 2FA for customer with ID                 '{_customerManager.GetUserId                     (User)}'.");

            throw new InvalidOperationException             ($"Unexpected error occurred disabling 2FA                 for customer with ID'{_customerManager                     .GetUserId(User)}'.");

        }

        _logger.LogInformation("Customer with ID         '{UserId}' has disabled 2fa.",            _customerManager.GetUserId(User));

        StatusMessage = "2fa has been disabled. You can         reenable 2fa when you setup an authenticator             app";

        return         RedirectToPage("./TwoFactorAuthentication");

    }

    We used the _logger object from the dependency injection (DI) to call the LogError method, which writes an error log in the current logging provider.

How it works…

We have preconfigured our sample Online Banking app to add Windows event logging by calling the ConfigureLogging method. The ConfigureLogging method will create an ILogger object for the Windows EventLog provider. We set the SourceName property of the ILogger object to OnlineBankingApp to identify the logs generated by our sample Online Banking application:

public static IHostBuilder CreateHostBuilder(string[] args) =>

    Host.CreateDefaultBuilder(args)        .ConfigureLogging(logging =>

        {

            logging.AddEventLog(eventLogSettings =>

            {

                eventLogSettings.SourceName =                    "OnlineBankingApp";

            });

        })

We also have configured informational logging by adding an entry in the Logging section of the appsettings.json file. This will create an OnlineBankingApp category with its log level set to Information:

{

  "Logging": {

    "EventLog": {

      "LogLevel": {

        "Default": "Warning",

        "OnlineBankingApp": "Information"

      }

    },    

With these settings in place, we can now use the Windows EventLog provider for our logging. The instance of the ILogger object, _logger, is already made available through DI. We now simply make a call to the ILogger object's LogError method in the lines of code where a critical exception occurred.

Note

It is important to note that the location and how the logs are stored are essential criteria. The configuration or the code shouldn't place the logs in the same location as the web server. We must implement proper access control to prevent unauthorized viewing of logs. There are open source and enterprise security solutions that provide tools to view, collect, and store logs securely.

Fixing insufficient logging of DB transactions

Basic DB transactions such as creating, reading, and deleting records are essential to have audit trails, especially when an error occurs as a DB function is performed.

In this recipe, we will fix the insufficient logging of a failed DB transaction, when a related exception is thrown.

How to do it…

Let's take a look at the steps for this recipe:

  1. From the starting exercise folder, launch VS Code by typing the following command:

    code .

  2. Open PagesBackupsEdit.cshtml.cs and notice a lack of DB operation logging in the OnPostAsync method:

    public async Task<IActionResult> OnPostAsync()

    {

        if (!ModelState.IsValid){

            return Page();

        }

        _context.Attach(Backup).State =        EntityState.Modified;

        try{

            await _context.SaveChangesAsync();

        }

        catch (DbUpdateException){

            if (!BackupExists(Backup.ID)){

                return NotFound();

            }

            else{

                throw;

            }

        }

        return RedirectToPage("./Index");

    }

    However, DB operations such as performing a backup and updating its related records should be logged.

  3. To fix the missing logging of high-value DB transactions, let's add a logger using the ILogger interface through DI. Begin by defining a _logger member of type ILogger:

    public class EditModel : PageModel

    {

        private readonly OnlineBankingApp.Data         .OnlineBankingAppContext _context;

        private readonly ILogger<EditModel> _logger;

    // code removed for brevity

  4. Next, inject the ILogger member into the EditModel constructor:

    public EditModel(OnlineBankingApp.Data     .OnlineBankingAppContext context,        ILogger<EditModel> logger)

    {

        _logger = logger;

        _context = context;

    }

  5. In the try-catch block, add the following lines of code:

    try{

        await _context.SaveChangesAsync();

    }

    catch (DbUpdateException ex){

        if (!BackupExists(Backup.ID)){

            _logger.LogError("Backup not found");

            return NotFound();

        }

        else{

            _logger.LogError($"An error occurred in

                backing up the DB { ex.Message } ");

            throw;

        }

    }

    Adding these lines of code will write an error log in the EventLog logging provider using the same logging settings that were explained in the preceding recipe.

How it works…

During a sensitive DB operation such as a DB backup, an unexpected error can occur. Such exceptions may cause a faulty system or—worse—an attack in our sample Online Banking app. We mitigate the risk of losing data integrity by keeping track of these DB operations and knowing when an event happened. We make a call to the LogError function to write a log into the Windows event log:

if (!BackupExists(Backup.ID)){

    _logger.LogError("Backup not found");

    return NotFound();

}

else{

    _logger.LogError($"An error occurred in backing up the         DB { ex.Message } ");

    throw;

}

As a best practice, we provide an appropriate error log message for specific exceptions (DbUpdateException) that we anticipate and, at the most, log only the Message property of the generic exception, avoiding revealing sensitive information in the logs that we create (more about best practices on exception handling in Chapter 13, Best Practices).

Fixing excessive information logging

As we learned in Chapter 4, Sensitive Data Exposure, ensuring you prevent the exposure of personal details is the key to keeping your application secure, and the same goes for logging information. While logs are helpful, there is also a risk involved in logging excessive data. Perpetrators will find ways to get useful information, and the log store is one source they will try to discover.

In this recipe, we will fix the excessive logging of information such as usernames and passwords.

How to do it…

Let's take a look at the steps for this recipe:

  1. From the starting exercise folder, launch VS Code by typing the following command:

    code .

  2. Open AreasIdentityPagesAccountLogin.cshtml.cs and locate the lines of code that send too much sensitive information into the logs:

        if (ModelState.IsValid)

        {

            // This doesn't count login failures towards         account lockout

            // To enable password failures to trigger         account lockout, set lockoutOnFailure: true

            var signInResult = await             _signInManager.PasswordSignInAsync                 (Input.Email, Input.Password,                     Input.RememberMe, lockoutOnFailure                         : false);

        if (signInResult.Succeeded)

            {

                _logger.LogInformation($"Customer with                 email { Input.Email } and password                     { Input.Password } logged in");

  3. To fix the issue, we replace the line with a proper log entry:

    if (ModelState.IsValid)

    {

        // This doesn't count login failures towards     account lockout     // To enable password failures to trigger account     lockout, set lockoutOnFailure: true

        var signInResult = await         _signInManager.PasswordSignInAsync             (Input.Email, Input.Password,                 Input.RememberMe, lockoutOnFailure:                    false);

        if (signInResult.Succeeded)

        {

            _logger.LogInformation("User logged in.");

            if (string.IsNullOrEmpty(HttpContext             .Session.GetString(SessionKeyName)))

            {

        HttpContext.Session.SetString         (SessionKeyName, Input.Email);

            }

    Refactoring the code to remove sensitive information such as usernames and passwords prevents an incident where a perpetrator can get hold of the log store and use the information gathered to exploit our sample Online Banking app.

How it works…

We can view the logs generated by our sample Online Banking app by opening the Windows Event Viewer via the Run command. Here are the steps to do this:

  1. Type Windows + R, and once the Run window shows up, as shown in the following screenshot, type eventvwr.msc:
    Figure 11.1 – Run command

    Figure 11.1 – Run command

  2. In Event Viewer (Local), expand Windows Logs, then Application. Look for the logs where the Source name is equivalent to OnlineBankingApp:
Figure 11.2 – Event Viewer

Figure 11.2 – Event Viewer

The informational logs you see in Event Viewer are the records generated by the LogInformation method call in the preceding recipe. We have modified the code to prevent explici.t logging of sensitive information such as a user's credentials. We use a generic informational message to remediate the issue of exposing details we would not want anyone to misuse.

Fixing a lack of security monitoring

Monitoring allows us to actively observe events that occur in our ASP.NET Core web applications. Missing out on incidents as they happen in real time can lead to an attacker causing more damage as each minute goes by. Developers must enable monitoring in their ASP.NET Core web applications to have early preventive detection.

In this recipe, we will fix a lack of security monitoring in our sample Online Banking web app by implementing Azure Application Insights.

How to do it…

Let's take a look at the steps for this recipe:

  1. From the starting exercise folder, launch VS Code by typing the following command:

    code .

  2. Navigate to Terminal | New Terminal in the menu or do this by simply pressing Ctrl + Shift + ' in VS Code.
  3. Install the Application Insights software development kit (SDK) by running the following command in the VS Code terminal:

    dotnet add package Microsoft.ApplicationInsights.AspNetCore

  4. Open Startup.cs and make a call to the AddApplicationInsightsTelemetry method under ConfigureServices:

    public void ConfigureServices(IserviceCollection     services)

    {

        services.AddApplicationInsightsTelemetry();

    // code removed for brevity

    The AddApplicationInsightsTelemetry method, as the name implies, will add an Insights telemetry collection to our sample Online Banking app.

  5. Open the appsettings.json file to add the instrumentation key:

    {

      "ApplicationInsights": {

        "InstrumentationKey": "My-Instrumentation-Key"

      },  

      "Logging": {

        "EventLog": {

          "LogLevel": {

            "Default": "Warning",

            "OnlineBankingApp": "Information"

          }

        },    

    Note

    To generate an instrumentation key, follow the Create an Application Insights resource instructions in the Microsoft official online documentation for Azure Monitor, found at https://docs.microsoft.com/en-us/azure/azure-monitor/app/create-new-resource.

  6. Type the following command in the terminal to build and run the sample app:

    dotnet run

  7. Open a browser and go to https://localhost:5001/:.

    Incoming requests will now be collected by the Application Insights SDK, including the ILogger logs with Medium, Error, and Critical severity.

How it works…

We implement telemetry collection by integrating our sample Online Banking web application with Azure Application Insights. Application Insights collects more than performance metrics and logs generated by our ILogger provider—it also performs analysis and application security detection. This cloud-based service sends alerts and notifications in the event of a security issue such as an unsecured form, insecure Uniform Resource Locator (URL) access, and shady user activity.

There's more…

The preceding steps for the recipe are there to enable the telemetry collection from the server side. Here are the steps to enable monitoring from the client side:

  1. Open Pages\_ViewImports.cshtml and inject th'e JavaScriptSnippet service from the Application Insights SDK:

    @using OnlineBankingApp

    @namespace OnlineBankingApp.Pages

    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

    @inject Microsoft.ApplicationInsights.AspNetCore.JavaScriptSnippet JavaScriptSnippet

  2. Open PagesShared\_Layout.cshtml and insert the JavaScript snippet in the head section, with a call to the Html.Raw helper method:

    ...

        <link rel="stylesheet" href="~/lib/bootstrap/        dist/css/bootstrap.min.css" />

        <link rel="stylesheet" href="~/css/site.css" />

        @Html.Raw(JavaScriptSnippet.FullScript)

    </head>

By placing the JavaScript in the _Layout.cshtml file, we enable client-side monitoring on all pages that use the layout template of our sample Online Banking web app.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.218.230