65.9K
CodeProject 正在变化。 阅读更多。
Home

SQL Server 暴力攻击检测:第 2 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.07/5 (9投票s)

2018 年 3 月 1 日

CPOL

3分钟阅读

viewsIcon

11441

downloadIcon

200

附加日志记录和数据收集

系列文章

引言

在本系列的第二部分中,我想介绍一些我一直在测试的附加功能,以使代码对开发人员更有用。据我所知,这些功能也适用于 SQL Server 2005 及更高版本的所有版本。

背景

最初的代码一直在我的家庭数据库服务器上运行,并定期阻止攻击 IP。 同时,我更深入地思考了可以使代码对开发人员更有用的附加功能。

我想到的一个用例是客户端或移动应用程序连接到 SQL Server 数据库,用户拥有自己的用户名和密码。 在用户意外多次输入错误密码的情况下,我们需要一种简单的方法在防火墙中解除阻止它们。 通常,用户身份验证 UI 包含密码重置功能,因此我们需要一种简单的方法来附加一些简单的代码以自动解除阻止它们。

此外,我认为记录阻止/解除阻止事件也很好,这样我就可以按 IP 获取统计信息,并查看是否有重复的违规者。

Using the Code

我添加了两个表来捕获有关失败登录尝试的附加数据。 BlockedClientDtl 捕获每次失败尝试使用的用户 ID,EventLog 记录由 CheckFailedLogins 存储过程完成的阻止/解除阻止操作。 我还修改了 BlockedClient 以记录特定的解除阻止日期时间值,以防我们要修改此值的计算方式(例如,按客户端 IP,可能延长重复违规者的阻止时间)。

CREATE TABLE BlockedClient
(
    IPAddress VARCHAR(15) NOT NULL PRIMARY KEY,
    LastFailedLogin DATETIME,
    UnblockDate DATETIME,
    FailedLogins INT,
    FirewallRule VARCHAR(255)
);

CREATE INDEX IX_BlockedClient_UnblockDate ON BlockedClient(UnblockDate);

CREATE TABLE BlockedClientDtl
(
    IPAddress VARCHAR(15) NOT NULL,
    Attempt INT NOT NULL,
    LogDate DATETIME,
    UserId VARCHAR(255),
    Message VARCHAR(512),
    PRIMARY KEY(IPAddress, Attempt),
    FOREIGN KEY(IPAddress) REFERENCES BlockedClient ON DELETE CASCADE
);

CREATE INDEX IX_BlockedClientDtl_UserId_LogDate ON BlockedClientDtl(UserId, LogDate);

CREATE TABLE EventLog
(
    LogId BIGINT NOT NULL PRIMARY KEY IDENTITY,
    LogDate DATETIME DEFAULT GETDATE(),
    IPAddress VARCHAR(15),
    Action VARCHAR(20),
    EventDesc VARCHAR(512)
);

CREATE INDEX IX_EventLog_IP_LogDate ON EventLog(IPAddress, LogDate);

BlockedClientDtl 的想法是能够在用户请求密码重置时运行如下查询

DELETE FROM BlockedClient
WHERE EXISTS (SELECT * FROM BlockedClientDtl
              WHERE BlockedClientDtl.IPAddress = BlockedClient.IPAddress
                AND BlockedClientDtl.UserId = 'JDOE')

当用户 JDOE 完成密码重置时,我们可以删除与用户 ID 关联的 BlockedClient 中的所有条目(并且可以选择仅删除基于 BlockedClientDtl.LogDate 的一段时间内的条目)。

最后,是我们 CheckFailedLogins SP 的修订代码

CREATE PROCEDURE CheckFailedLogins
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @UnblockDate DATETIME
    DECLARE @LookbackDate DATETIME
    DECLARE @MaxFailedLogins INT
    DECLARE @FailedLogins TABLE
    (
         LogDate datetime,
         ProcessInfo varchar(50),
         Message text
    );
    DECLARE @FailedLoginClientDtl TABLE
    (
      IPAddress VARCHAR(15),
        LogDate DATETIME,
        UserID VARCHAR(128),
        Message VARCHAR(1000)
    );
    
    SELECT @LookbackDate = dateadd(second, -ConfigValue, getdate())
    FROM Config
    WHERE ConfigID = 1

    SELECT @MaxFailedLogins = ConfigValue
    FROM Config
    WHERE ConfigID = 2

    SELECT @UnblockDate = CASE WHEN ConfigValue > 0 THEN DATEADD(hour, ConfigValue, getdate()) END
    FROM Config
    WHERE ConfigID = 3

    INSERT INTO @FailedLogins -- Read current log
    exec sp_readerrorlog 0, 1, 'Login failed';

    INSERT INTO @FailedLoginClientDtl
    SELECT ltrim(rtrim(substring(CONVERT(varchar(1000), Message),
        charindex('[CLIENT: ', CONVERT(varchar(1000), Message)) + 9,
        charindex(']', CONVERT(varchar(1000), Message)) - 9 - 
          charindex('[CLIENT: ', CONVERT(varchar(1000), Message))))) as IPAddress,
        LogDate,
        CASE WHEN charindex('Login failed for user ''', CONVERT(varchar(1000), Message)) > 0 THEN
        ltrim(rtrim(substring(CONVERT(varchar(1000), Message),
        charindex('Login failed for user ''', CONVERT(varchar(1000), Message)) + 23,
        charindex('''. Reason:', CONVERT(varchar(1000), Message)) - 23 - 
        charindex('Login failed for user ''', CONVERT(varchar(1000), Message))))) END AS UserId,
        Message
    FROM @FailedLogins
    WHERE (Message like '%Reason: An error occurred while _
                           evaluating the password.%' -- Some filter criteria
            OR Message like '%Reason: Could not find a login matching the name provided.%'
            OR Message like '%Reason: Password did not match that for the login provided.%'
            OR Message LIKE '%Login failed. The login is from an untrusted domain _
                             and cannot be used with Windows authentication.%')
        AND LogDate >= @LookbackDate

    INSERT INTO BlockedClient(IPAddress, LastFailedLogin, UnblockDate, FailedLogins)
    OUTPUT INSERTED.IPAddress, 'Block', 'Blocked client ' + INSERTED.IPAddress + _
          ' after ' + CONVERT(VARCHAR(10), INSERTED.FailedLogins) + ' failed login attempts.'
    INTO EventLog(IPAddress, Action, EventDesc) -- Record block event
    SELECT IPAddress,
        MAX(LogDate) AS LastFailedLogin,
        @UnblockDate,
        COUNT(*) AS FailedLogins
    FROM @FailedLoginClientDtl d
    WHERE NOT EXISTS (SELECT * FROM Whitelist l -- Check against whitelist
                      WHERE l.IPAddress = d.IPAddress)
      AND NOT EXISTS (SELECT * FROM BlockedClient c -- ignore already blocked clients
                      WHERE c.IPAddress = d.IPAddress)
      AND IPAddress <> '<local machine>' -- ignore failed logins from local machine
    GROUP BY IPAddress
    HAVING COUNT(*) >= @MaxFailedLogins -- Check against number of failed logins config

    INSERT INTO BlockedClientDtl(IPAddress, Attempt, LogDate, UserId, Message)
    SELECT IPAddress,
      Attempt,
        LogDate,
        UserID,
        Message
    FROM
    (
        SELECT IPAddress,
            ROW_NUMBER()OVER(PARTITION BY IPAddress ORDER BY LogDate) AS Attempt,
            LogDate,
            UserID,
            Message
        FROM @FailedLoginClientDtl d
        WHERE EXISTS (SELECT * FROM BlockedClient c
                                    WHERE c.IPAddress = d.IPAddress)
    )AS t
    WHERE NOT EXISTS (SELECT * FROM BlockedClientDtl dtl
                      WHERE t.IPAddress = dtl.IPAddress
                        AND t.Attempt = dtl.Attempt)

    DELETE FROM BlockedClient -- Delete entries older than the delete config set if > 0
    OUTPUT DELETED.IPAddress, 'Unblock', 'Unblocked client ' + DELETED.IPAddress  + '.'
    INTO EventLog(IPAddress, Action, EventDesc) -- Record unblock event
    WHERE UnblockDate < getdate()
END

对于 BlockedClient 上的插入和删除操作,我使用了使用 OUTPUT 子句时可用的 INSERTEDDELETED 特殊表,将记录插入到 EventLog 表中,以便我可以看到阻止/解除阻止事件的历史记录。

进一步改进

我们可以通过观察 EventLog 中的模式来发挥客户端特定阻止参数的创造力。 也许每次 IP 被阻止时,都会逐步增加该 IP 的阻止时间。 无论如何,如果我们想更深入地挖掘或排除意外行为,那么了解这些事件的可见性是很有用的。

不幸的是,我们已经几乎达到了可以从 sp_readerrorlog 获取的信息的限制。 在本系列的第三部分中,我将介绍一个完全不同的实现,该实现使用仅在标准版和企业版中可用的服务代理。 通过这种方法,我们将能够收集更多关于登录事件的信息,并直接在这些事件上触发客户端阻止。 话虽如此,我想首先介绍所有可以在 SQL Server Express 版本中使用的功能,以便更多的人可以利用它们,并希望能够立即改善其数据库的安全性。

最终版本可在此处获取

© . All rights reserved.