使用监控工具SQL Monitor,检查失败的服务器登录、服务器错误和警告(上)

?本教程提供了一个解决方案,使用PowerShell和SQL Monitor,可以迅速警告您一系列Windows错误、警告和关键事件,包括失败的服务器登录尝试,这将伴随着对承载您的SQL Server实例的Windows Server的强力密码攻击。这是上半部分内容~

SQL Monitor是一个SQL Server监控工具。它可以监控SQL Servers的健康状况和活动,并通过电子邮件为您发送监测结果和建议。

SQL Monitor正式版

本教程提供了一个解决方案,使用PowerShell和SQL Monitor,可以迅速警告您一系列Windows错误、警告和关键事件,包括失败的服务器登录尝试,这将伴随着对承载您的SQL Server实例的Windows Server的强力密码攻击。本文为上半部分内容~

每当听到由Windows Server主机上的强行密码攻击引发的又一次SQL Server漏洞时,标准反应就是在激动中挥舞着双手,同时解释了持续不断地自动检查服务器日志以获取警告的必要性。

最后,拿出一个解决方案。使用Get-WinEvent cmdlet,并在散列表的帮助下,使用PowerShell脚本读取和过滤Windows事件日志数据。将数据保存在SQL Server数据库中,因此可以在那里进行扫描,并确保有永久的事件记录。我编写了一个简单的SQL Monitor自定义指标,该指标返回最近10分钟内记录的Windows事件数。SQL Monitor将按计划收集此数据,并在发生这些事件时通知我。

有了这样的解决方案,您将在五分钟之内收到有关安全日志中的所有错误,警告和关键事件以及选择的其他任何日志的警告,并且您将看到大量的失败的登录尝试这将伴随强力破解密码攻击。我希望即使读者使用其他警报系统,该解决方案仍然对读者有用,因为大多数任务是相同的。

30word-image-23.png

获取Windows安全日志事件

Windows安全日志事件很多。其中很多都没有任何意义,但是有些是监视安全性的瑰宝。有人可能以为,安全事件的级别可以表明其重要性,但事实并非如此。失败的登录尝试,在任何暴力攻击中都可能发生,被视为“信息”。但是,一般而言,日志错误和警告也很有用,并在许多情况下为我节省了时间。

通过PowerShell阅读Windows事件和经典日志

SQL不适合此任务,因为只能通过读取SQL Server错误日志和SQL Agent日志xp_readerrorlog。您在SSMS中拥有日志文件查看器,可用于读取经典日志,这对于临时调查非常有用。有几种方法可以在PowerShell中获取此信息,如Laerte Junior的The PoSh DBA – Reading and Filtering Errors中所述。

该Get-WinEventcmdlet可能是从本地服务器上的100多个事件日志获取信息的,包括经典的最佳工具系统安全应用程序日志。它还会读取由更高版本的Windows事件日志技术生成的事件日志,以及由Windows事件跟踪生成的最新事件。单个日志可以包含数千个条目。

如果您以Admin用户身份运行,此cmdlet允许您列出和调查日志并读取内容。这里有很多信息,过滤是必不可少的,尤其是当您在几台服务器上一个接一个地读取日志时。如果您明智的应用过滤器,Get-WinEvent可以快速工作。在找到搜索模式之前,可能需要进行一些试验,以使其找到搜索日志的最佳方式。如果您做对了,那就太快了。

Get-WinEvent查询

有三种指定查询的方式。您可以使用XPath查询或结构化XML查询。对于像我这样的普通人,有一些简单的hash表过滤器,它们的工作原理与溅射参数非常相似,只是参数并未全部公开。

有几种不同的方法来切割这个巨大的披萨事件。您可以通过日志名称列表、提供程序名称、关键字枚举值、事件ID、严重性级别或从已存档经典日志的路径列表中进行选择。您可以指定时间段的开始和结束,或指定生成错误的用户ID。这种多功能性适合我们。

创建hash表过滤器很简单。在这里,我们只是在寻找过去24小时内发生的严重、严重或错误级别的可怕“经典”事件。如果需要扫描其他日志,只需将它们添加到列表中。然后,我们简单地将过滤器作为Get-WinEvents的FilterHashTable参数。

$Search = @{    LogName = 'application', 'security', 'system';    # we search the application, security and system logs    Level = 1, 2, 3;    # Verbose 5, Informational 4, Warning 3, Error 2, Critical 1, LogAlways 0     # we do events and errors of warning, error and critical levels     StartTime = (get-date) - (New-TimeSpan -Days 1)    # We go just one day back as we are calling this regularly  };  Get-WinEvent  -FilterHashTable $Search -MaxEvents 1000 |    Select Id, TimeCreated, Level, LevelDisplayName, Message, ProviderName, LogName

虽然这将为您提供基本的警告和错误,但不会使您登录失败。这些不是警告,而是信息事件。这需要再次搜索我们要监视的任何特定安全事件,但这些事件不被视为警告。

$FailedLogins = @{    LogName = 'security';    ID = 4625; #add any other security events you want that are informational    Level = 0;    StartTime = (get-date) - (New-TimeSpan -Days 1)  }  Get-WinEvent -MaxEvents 1000 -FilterHashTable $FailedLogins |    Select Id, TimeCreated, level, LevelDisplayName, Message, ProviderName, LogName

我们将在此时停止,但是您可能会看到您感兴趣的其他安全事件。我还没有演示过,但是您可以通过Get-WinEvent对整个hash表进行管道内衬来进行很多快速搜索。您甚至可以在服务器列表上运行搜索。

SQL Monitor正式版

PowerShell脚本

现在,我们只需要按计划执行这些查询,并将结果存储在SQL Server数据库中(在我的示例中称为ServerEvents)。您必须创建数据库,但是表(名为临时表EventsStaging和名为的目标表Events)是由计划的进程创建的。临时表是使用Write-SqlTableData cmdlet的-force参数自动创建的。

该脚本使用sqlserver提供程序来完成无聊的工作,即将包含事件日志数据的PowerShell对象复制到临时表中,然后在服务器上执行SQL代码以创建目标表,并将在其中找到的所有事件添加到该表中。尚未存储在目标中的临时表。

您必须在与ServerEvents数据库相同的服务器上运行脚本。如果您将Get-WinEvent监视事件的服务器指定为参数,则可以远程运行它。

分配来运行此计划任务的管理员用户必须是SQL Server登录名,该登录名仅在ServerEvents数据库上是dbo角色的成员。我们的脚本需要执行常规工作,以获取此admin用户的凭据,该凭据以加密形式存储在Windows用户配置文件目录中,该文件的位置通过环境变量来引用$env:USERPROFILE。仅当您希望避免将SQL Server登录名分配给本地Windows用户时才需要这样做。首次运行脚本时,系统会要求您输入用户密码。希望这对任何用户仅发生一次。

或者,如果您使用Windows身份验证,则可以为您分配给该任务的用户提供此登录名和该数据库的dbo用户角色,而您将其$SQLUserName留空。

$SQLUserName = 'PhilFactor'  $SQLInstance = 'MyServer'  $SQLDatabase = 'ServerEvents'  $StagingTableName = 'EventsStaging'  $DestinationTableName = 'Events'  $Logfile = 'C:ScriptsGetWindowErrors.log'  $Errors = @()  "$(get-date): started out getting errors">>$Logfile  Import-Module sqlserver -DisableNameChecking     `          -ErrorAction silentlycontinue    `          -ErrorVariable +Errors #load the SQLPS functionality  set-psdebug -strict  $ErrorActionPreference = "stop"  $SqlEncryptedPasswordFile = `  "$env:USERPROFILE$($SqlUserName)-$($SQLInstance).txt"  # test to see if we know about the password in a secure string stored in the user area  if (Test-Path -path $SqlEncryptedPasswordFile -PathType leaf)  {    #has already got this set for this login so fetch it    $Sqlencrypted = Get-Content $SqlEncryptedPasswordFile | ConvertTo-SecureString    $SqlCredentials = `    New-Object System.Management.Automation.PsCredential($SqlUserName, $Sqlencrypted)  }  else #then we have to ask the user for it  {    #hasn't got this set for this login    $SqlCredentials = get-credential -Credential $SqlUserName    $SqlCredentials.Password | ConvertFrom-SecureString |    Set-Content $SqlEncryptedPasswordFile  }  "$(get-date): got credentials for $SqlUserName">>$Logfile  <#   #>  $Search = @{    LogName = 'application', 'security', 'system';    # we search the application, security and system logs    Level = 1, 2, 3;    # we do events and errors of warning, error and critical     StartTime = (get-date) - (New-TimeSpan -Days 1)    # We go just one day back  };  $FailedLogins = @{    LogName = 'security';    ID = 4625; #add any other security events you want that are    Level = 0;    StartTime = (get-date) - (New-TimeSpan -Days 1)  }  try  {    $Events = Get-WinEvent -MaxEvents 1000 -FilterHashTable $Search    `                 -ErrorAction silentlycontinue    `                 -ErrorVariable +Errors |    Select Id, TimeCreated, Level, LevelDisplayName, Message, ProviderName, LogName  }  Catch  {    $ThisError = "$(get-date): Failed to get WinEvent $($_.ErrorDetails.Message) "    $errors += $ThisError    $ThisError>>$Logfile    >>$Logfile  }  try  {    $Events += Get-WinEvent -MaxEvents 1000 -FilterHashTable $FailedLogins   `                -ErrorAction silentlycontinue    `                -ErrorVariable +Errors |    Select Id, TimeCreated, level, LevelDisplayName, Message, ProviderName, LogName  }  Catch  {    $ThisError = "$(get-date): Failed to get WinEvent Security log $($_.ErrorDetails.Message) "    $errors += $ThisError    $ThisError>>$Logfile  }  if ($errors[0] -ilike "*No events were found*" -and $errors.Count -eq 1)  { $Errors = @() }  if (($Events.Count -gt 0) -and ($Errors.count -eq 0))  {    Write-SqlTableData -inputData $Events `               -ServerInstance $SQLInstance -database $SQLDatabase `               -Credential $SQLCredentials ` -SchemaName "dbo" -TableName $StagingTableName -Force    `               -ErrorAction silentlycontinue    `               -ErrorVariable +Errors  }  $sql =@"  USE [$SQLDatabase]  GO  IF Object_Id('dbo.$DestinationTableName') IS NULL    begin    CREATE TABLE [dbo].[$DestinationTableName](      [Id] [INT] NOT NULL,      [TimeCreated] [DATETIME2](7) not NULL,      [Level] [TINYINT] NOT NULL,      [LevelDisplayName] [NVARCHAR](40) not NULL,      [Message] [NVARCHAR](4000) NOT NULL,      [ProviderName] [NVARCHAR](40) NOT NULL,      [LogName] [NVARCHAR](40) NOT NULL    ) ON [PRIMARY]    END  GO  INSERT INTO [$DestinationTableName](Id, TimeCreated, [Level], LevelDisplayName, [Message],         ProviderName, LogName)  SELECT $StagingTableName.Id, $StagingTableName.TimeCreated, $StagingTableName.Level,         $StagingTableName.LevelDisplayName, Left($StagingTableName.Message,4000),         Left($StagingTableName.ProviderName,40), Left($StagingTableName.LogName,40)       FROM $StagingTableName  LEFT OUTER JOIN [$DestinationTableName]  ON $DestinationTableName.Id = $StagingTableName.Id  AND $DestinationTableName.TimeCreated = $StagingTableName.TimeCreated  WHERE $DestinationTableName.message IS null  TRUNCATE TABLE $StagingTableName --because you don't need that data again.  "@  if ($Errors.count -eq 0)  {    Invoke-Sqlcmd -Query $sql -ServerInstance $SQLInstance  `            -Credential $SQLCredentials -database $SQLDatabase   `            -ErrorAction silentlycontinue    `            -ErrorVariable +Errors  }  <# We collect all the soft errors and deal with them here.#>  if ($errors.Count -gt 0)  {    $errors | foreach {      "$((Get-Date).ToString()): $($_) the task was aborted">>$Logfile;    }  };  "$(get-date): checked for errors, found $($events.Count)">>$Logfile

我将其放在Windows任务计划程序上,每隔5分钟运行一次,这就是为什么将运行此脚本产生的所有错误和警告通过管道传输到本地错误日志文件的原因(否则您将看不到它们!)

这是一个最好的过程,需要分几个阶段进行,并且要一直进行测试。首先,在计划程序中,在服务器上的PowerShell ISE中运行任务,并以您将分配为运行PowerShell任务的用户ID登录。然后,以同一用户的身份使用PowerShell控制台,再使用命令控制台,最后在调度程序上,以五分钟的间隔运行它,并检查本地错误日志中是否有任何错误和报告。

确保此功能正常运行的明显测试是尝试使用错误的ID或密码登录服务器,并查看events表中是否有任何内容。

31word-image-24.png

您还可以添加各种虚假错误以确保将其记录下来。

New-EventLog –LogName Application –Source PowerShellScript  Write-EventLog -LogName "Application"    `         -Source PowerShellScript   `         -EventID 3001 -EntryType Warning     `         -Message "A melancholy misadventure has struck my code."  Write-EventLog -LogName "Application"    `         -Source PowerShellScript   `         -EventID 3001 -EntryType Error     `         -Message "My code has been sadly struck by misfortune."

五分钟后,您需要在SSMS中检查事件查看器和SQL Server数据库,以确保所有事件都已转移。

32word-image-25.png

一旦确定这是可行的,就可以在SQL Monitor端进行操作。

本教程内容尚未完结,敬请期待下半部分内容——创建SQL Monitor自定义指标~感兴趣的朋友可以下载SQL Monitor正式版尝试一下~

相关内容推荐:

监控工具SQL Monitor:使用SQL Monitor跟踪数据库上的活动会话数

使用SQL Server监控工具SQL Monitor,监视Azure SQL数据库的性能问题(上)

使用SQL Server监控工具SQL Monitor,监视Azure SQL数据库的性能问题(下)


想要购买SQL Monitor正版授权,或了解更多产品信息请点击“咨询在线客服”

Zend-Studio-640×220.png

标签:

来源:慧都

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2019年8月19日
下一篇 2019年8月19日

相关推荐

发表回复

登录后才能评论