对 ASP.NET Core web 应用的攻击可能随时发生。开发人员必须授权其安全团队通过从 web 应用生成足够的日志来重构事件。记录正确的信息将有助于确定事件的详细信息,并为审计目的识别关键数据。未能记录关键安全信息的缺点是,安全团队无法生成正确的分析或报告。但是,过多的日志记录可能会导致敏感数据泄露。只有通过积极监测,才能对此类安全事件采取必要和立即的应对措施。开发人员必须在 ASP.NET Core web 应用生成的日志中启用监视,以实现更实时的防御。
在本章中,我们将介绍以下配方:
- 修复异常记录不足的问题
- 修复了数据库(DB事务记录不足的问题
- 修复过多的信息记录
- 修复缺乏安全监控的问题
在本章结束时,您将了解如何在我们的示例网上银行应用中正确添加适当的异常记录,如何记录关键 DB 事务,如何防止记录过多的数据或信息,以及如何启用安全监控。
这本书的编写和设计是为了使用Visual Studio 代码(VS 代码)、Git 和.NET Core 5.0。配方中的代码示例主要在 ASP.NET Core Razor 页面中提供。示例解决方案还使用 SQLite 作为 DB 引擎,以简化设置。本章的完整代码示例可在上找到 https://github.com/PacktPublishing/ASP.NET-Core-Secure-Coding-Cookbook/tree/main/Chapter11 。
安全相关事件,如用户身份验证或启用和禁用双因素身份验证(2FA)-当发生时,必须记录并跟踪。为了了解安全事件发生时的事件顺序,这些事件对于审核是必不可少的。
在此配方中,我们将利用 ASP.NET Core 的内置日志提供程序修复安全相关异常的日志记录不足问题。
对于本章的食谱,我们需要一个在线银行应用示例。
打开命令 shell 并通过克隆ASP.NET-Core-Secure-Coding-Cookbook
存储库下载示例网银应用,如下所示:
git clone https://github.com/PacktPublishing/ASP.NET-Core-Secure-Coding-Cookbook.git
运行示例应用以验证没有生成或编译错误。在命令 shell 中,导航到位于\Chapter11\insufficient-logging-exception\before\OnlineBankingApp
的示例应用文件夹,并运行以下命令:
dotnet build
dotnet build
命令将构建示例OnlineBankingApp
项目及其依赖项。
让我们来看看这个食谱的步骤:
-
在开始练习文件夹中,通过键入以下命令启动 VS 代码:
code .
-
打开
Areas\Identity\Pages\Account\Manage\Disable2fa.cshtml.cs
并注意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(); }
下的代码
-
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"); }
这两种方法都有一行代码,其中抛出了一个
InvalidOperationException
异常。异常表示尝试禁用 2FA 但失败。应将这些事件视为异常并记录。这些事件可视为异常,应记录每一个事件。 -
为了修复事件日志记录的不足,我们重构了
OnGet
方法,并在抛出InvalidOperationException
异常时添加日志记录: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(); }
-
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"); }
我们使用依赖注入(DI中的
_logger
对象调用LogError
方法,该方法在当前日志提供程序中写入错误日志。
我们已通过调用ConfigureLogging
方法预先配置了我们的示例网上银行应用,以添加 Windows 事件日志记录。ConfigureLogging
方法将为 WindowsEventLog
提供程序创建一个ILogger
对象。我们将ILogger
对象的SourceName
属性设置为OnlineBankingApp
,以识别样本网上银行应用生成的日志:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args).ConfigureLogging(logging =>
{
logging.AddEventLog(eventLogSettings =>
{
eventLogSettings.SourceName = "OnlineBankingApp";
});
})
我们还通过在appsettings.json
文件的Logging
部分添加一个条目来配置信息日志记录。这将创建一个日志级别设置为Information
的OnlineBankingApp
类别:
{
"Logging": {
"EventLog": {
"LogLevel": {
"Default": "Warning",
"OnlineBankingApp": "Information"
}
},
有了这些设置,我们现在可以使用 WindowsEventLog
提供程序进行日志记录。ILogger
对象的实例_logger
已经通过 DI 提供。我们现在只需在发生严重异常的代码行中调用ILogger
对象的LogError
方法。
笔记
需要注意的是,日志的位置和存储方式是基本标准。配置或代码不应将日志放置在与 web 服务器相同的位置。我们必须实施适当的访问控制,以防止未经授权查看日志。有一些开源和企业安全解决方案提供了安全查看、收集和存储日志的工具。
基本数据库事务如创建、读取和删除记录对于审计跟踪至关重要,尤其是在执行数据库功能时发生错误时。
在此配方中,我们将修复在引发相关异常时失败的 DB 事务日志记录不足的问题。
让我们来看看这个食谱的步骤:
-
在开始练习文件夹中,通过键入以下命令启动 VS 代码:
code .
-
Open
Pages\Backups\Edit.cshtml.cs
and notice a lack of DB operation logging in theOnPostAsync
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"); }
但是,应记录数据库操作,如执行备份和更新其相关记录。
-
为了修复丢失的高值 DB 事务日志记录,让我们通过 DI 使用
ILogger
接口添加一个记录器。首先定义类型为ILogger
:public class EditModel : PageModel { private readonly OnlineBankingApp.Data .OnlineBankingAppContext _context; private readonly ILogger<EditModel> _logger; // code removed for brevity
的
_logger
成员 -
接下来,将成员注入
EditModel
构造函数中:public EditModel(OnlineBankingApp.Data .OnlineBankingAppContext context, ILogger<EditModel> logger) { _logger = logger; _context = context; }
-
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; } }
添加这些代码行将在
EventLog
日志记录提供程序中使用前面配方中说明的相同日志记录设置写入错误日志。
在敏感数据库操作(如数据库备份)期间,可能会发生意外错误。此类异常可能会在我们的示例网上银行应用中导致系统故障或更糟的攻击。我们通过跟踪这些数据库操作并了解事件发生的时间来降低丢失数据完整性的风险。我们调用LogError
函数将日志写入 Windows 事件日志:
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;
}
作为最佳实践,我们为我们预期的特定异常(提供适当的错误日志消息,最多只记录一般异常的Message
属性,避免在我们创建的日志中披露敏感信息(更多关于中异常处理的最佳实践)第 13 章、最佳实践)。
正如我们在第 4 章敏感数据泄露中所了解到的,确保您防止个人信息泄露是确保应用安全的关键,日志信息也是如此。虽然日志很有用,但记录过多数据也有风险。犯罪者会找到获取有用信息的方法,而日志存储是他们试图发现的来源之一。
在此配方中,我们将修复过度记录的信息,如用户名和密码。
让我们来看看这个食谱的步骤:
-
在开始练习文件夹中,通过键入以下命令启动 VS 代码:
code .
-
打开
Areas\Identity\Pages\Account\Login.cshtml.cs
并找到向日志发送过多敏感信息的代码行: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");
-
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); }
重构代码以删除用户名和密码等敏感信息,可防止犯罪者获得日志存储并使用收集到的信息利用我们的示例网上银行应用进行攻击。
我们可以通过Run
命令打开 Windows事件查看器来查看我们的示例网银应用生成的日志。以下是执行此操作的步骤:
-
Type Windows + R, and once the Run window shows up, as shown in the following screenshot, type
eventvwr.msc
:图 11.1–运行命令
-
在事件查看器(本地)中,展开Windows 日志,然后展开应用。查找源名称相当于
OnlineBankingApp
的日志:
图 11.2–事件查看器
您在事件查看器中看到的信息日志是前面配方中LogInformation
方法调用生成的记录。我们修改了代码,以防止对敏感信息(如用户凭据)进行显式.t 日志记录。我们使用一个通用的信息消息来解决暴露细节的问题,我们不希望任何人滥用这些细节。
监控允许我们主动观察 ASP.NET Core web 应用中发生的事件。错过实时发生的事件会导致攻击者每分钟造成更大的伤害。开发人员必须在其 ASP.NET Core web 应用中启用监视,以便进行早期预防性检测。
在此配方中,我们将通过实现Azure Application Insights来修复我们示例网上银行 web 应用中缺乏安全监控的问题。
让我们来看看这个食谱的步骤:
-
在开始练习文件夹中,通过键入以下命令启动 VS 代码:
code .
-
导航至菜单中的终端****新终端,或在 VS 代码中按Ctrl+Shift+即可。 ** 通过在 VS 代码终端中运行以下命令来安装应用洞察软件开发工具包(SDK):
dotnet add package Microsoft.ApplicationInsights.AspNetCore
- Open
Startup.cs
and make a call to theAddApplicationInsightsTelemetry
method underConfigureServices
:
public void ConfigureServices(IserviceCollection services) { services.AddApplicationInsightsTelemetry(); // code removed for brevity
顾名思义,
AddApplicationInsightsTelemetry
方法将为我们的样本网上银行应用添加一个 Insights 遥测采集。- Open the
appsettings.json
file to add the instrumentation key:
{ "ApplicationInsights": { "InstrumentationKey": "My-Instrumentation-Key" }, "Logging": { "EventLog": { "LogLevel": { "Default": "Warning", "OnlineBankingApp": "Information" } },
笔记
要生成插装密钥,请按照 Microsoft Azure Monitor 官方在线文档中的创建应用洞察资源说明进行操作,该文档位于https://docs.microsoft.com/en-us/azure/azure-monitor/app/create-new-resource 。
- 在终端中键入以下命令以构建并运行示例应用:
dotnet run
- Open a browser and go to
https://localhost:5001/:
.
传入请求现在将由 Application Insights SDK 收集,包括具有
Medium
、Error
和Critical
严重性的ILogger
日志。* - Open
*## 它是如何工作的…
我们通过将示例网上银行 web 应用与Azure 应用洞察集成来实现遥测采集。Application Insights 收集的不仅仅是我们的ILogger
提供商生成的性能指标和日志,它还执行分析和应用安全检测。此基于云的服务在出现安全问题时发送警报和通知,如不安全的表单、不安全的统一资源定位器(URL)访问和可疑的用户活动。
配方的前面步骤用于从服务器端启用遥测采集。以下是从客户端启用监视的步骤:
-
打开
Pages\_ViewImports.cshtml
并从 Application Insights SDK 注入JavaScriptSnippet
服务:@using OnlineBankingApp @namespace OnlineBankingApp.Pages @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @inject Microsoft.ApplicationInsights.AspNetCore.JavaScriptSnippet JavaScriptSnippet
-
打开
Pages\Shared\_Layout.cshtml
并在head
部分插入 JavaScript 片段,同时调用Html.Raw
助手方法:... <link rel="stylesheet" href="~/lib/bootstrap/ dist/css/bootstrap.min.css" /> <link rel="stylesheet" href="~/css/site.css" /> @Html.Raw(JavaScriptSnippet.FullScript) </head>
通过在_Layout.cshtml
文件中放置 JavaScript,我们可以在使用我们的示例网上银行 web 应用的布局模板的所有页面上启用客户端监控。*