Обнаружение брут форс атаки SQL Server: часть 1

Tags: SQL Server, SQL

При развертывании базы данных SQL Server с возможностью клиентского доступа через Интернет, всегда лучше использовать VPN для подключения клиентов к локальной сети базы данных. Логины Windows Active Directory также могут применяться для доступа, чтобы можно было использовать безопасность AD (например, блокировку учетной записи после неудачных попыток входа в систему). Большинство экспертов по безопасности и администраторов баз данных настоятельно рекомендуют не открывать порт, который прослушивает SQL Server (по умолчанию 1433) в Интернете. Это связано с широким распространением автоматических сканеров портов, которые просматривают Интернет для открытых портов SQL Server и пытаются атаковать методом грубой силой или применить словарную атаку, как правило, на учетной записи sa.

Однако, иногда открытие интернет-доступа к SQL Server неизбежно из-за конструктивных характеристик приложения или устаревшего кода, который нельзя изменить. Возможно, мобильное приложение подключается к базе данных, делая невыполнимым или невозможным  ограничение доступа только к установленному списку IP-адресов, одобренному для сети или серверного брандмауэра. Внешний доступ в сочетании с доступом к учетной записи входа в SQL Server может сделать базу данных особенно уязвимой для атак с грубой силой, поскольку SQL Server не имеет встроенных функций для обнаружения последовательных неудачных входов и впоследствии отключает учетную запись или блокирует нарушающего клиента. Хуже всего, когда включена учетная запись sa и разрешен удаленный доступ к серверу; если унаследованное приложение использует этот вид доступа к базе данных, системный администратор будет в затруднительном положении, пытаясь поддерживать безопасность сервера, так как злоумышленник может свободно пытаться использовать столько попыток входа в систему, сколько ему потребуется, чтобы угадать пароль. ьзовать столько попыток входа в систему, сколько захочет угадать пароль. Лучшее, что можно сделать, это периодическое рассмотрение журналов событий SQL Server и ручное внесение конфликтующего IP-адреса в брандмауэр Windows или пограничный брандмауэр сети. Но даже это дает злоумышленнику достаточно времени, чтобы попробовать десятки тысяч попыток входа в систему. Даже если учетная запись sa отключена, и злоумышленник должен угадать логин, эти неудачные попытки входа в систему могут излишне тратить ресурсы на целевом сервере.

Мы можем обнаружить и автоматически заблокировать клиентов, пытающихся провести атаку грубой силой на нашу базу данных, с помощью T-SQL на любой версии SQL выше 2005 года. SQL Server имеет возможность как читать журналы событий в T-SQL с помощью sp_readerrorlog, так и выполнять команды командной оболочки с помощью xp_cmdshell. Чтение журнала событий может помочь извлечь неудачные попытки входа в систему и получить IP-адрес удаленного клиента, а xp_cmdshell можно использовать для вызова netsh advfirewall для автоматического добавления правил блокировки в брандмауэре Windows. С помощью некоторого дополнительного кода обработки мы можем включить конфигурации для количества неудачных попыток обнаружения перед блокировкой IP-адреса нарушителя и для освобождения через определенное время заблокированных записей.

Бэкграунд

Я впервые заметил проблему попыток входа грубой силой в базе данных SQL Server, которую я запускаю в своей домашней сети. Хотя база данных не используется для производственных целей, мне понравилось держать ее открытой в Интернете для моего собственного тестирования и разработки приложений. Однажды я просматривал журналы событий Windows на сервере, устраняя другие проблемы, и я заметил следующее:

Несколько тысяч записей журнала приложений для SQL Server; это тысячи неудачных попыток входа в систему с нескольких IP-адресов! Я заблокировал их в своем брандмауэре, но вскоре было показано  больше неудачных попыток с других IP-адресов. Я удалил порт в своем брандмауэре и решил разрешить доступ только по моему VPN-соединению, но начал думать о других способах упрощения SQL Server в том случае, если внешний доступ через пересылку порта был единственным вариантом для разработчика или администратора системы. Я знал о сценариях, написанных в PowerShell, которые сканируют журналы событий для неудачных соединений RDP и блокируют IP-адреса в брандмауэре Windows. После небольшого исследования я решил, что SQL Server обладает необходимой функциональностью для сканирования журналов событий и запускает netsh для создания правил брандмауэра Windows. Дополнительная логика для хранения конфигураций и управления списком блоков может быть легко обработана в T-SQL.

Использование кода

Настройка сервера

Во-первых, нам нужно разрешить доступ к xp_cmdshell (который отключен по умолчанию) и включить ведение журнала неудачных попыток входа в экземпляр SQL Server. Выполните следующий код из учетной записи sa или admin:

USE [master]

GO

/*

Enable auditing of failed logins and use of command shell on database.

*/

EXEC xp_instance_regwrite N'HKEY_LOCAL_MACHINE', N'Software\Microsoft\MSSQLServer\MSSQLServer', N'AuditLevel', REG_DWORD, 2

GO

 

EXEC sp_configure 'show advanced options', 1;

GO

 

RECONFIGURE;

GO

 

EXEC sp_configure'xp_cmdshell', 1

GO

 

RECONFIGURE

GO

 

Перезапустите службу MSSQL, чтобы изменения вступили в силу.

 

Создание таблиц

Затем мы создаем новую базу данных BruteForceAttackMonitor и три таблицы для хранения заблокированных IP-адресов, некоторых конфигураций и списка белых IP-адресов.

CREATE DATABASE [BruteForceAttackMonitor]

GO

 

ALTER DATABASE [BruteForceAttackMonitor] SET RECOVERY SIMPLE

GO

 

USE [BruteForceAttackMonitor]

GO

 

SET ANSI_NULLS ON

GO

 

SET QUOTED_IDENTIFIER ON

GO

 

/*

Create tables

*/

 

CREATE TABLE BlockedClient

(

   IPAddress VARCHAR(15) NOT NULL PRIMARY KEY,

   LastFailedLogin DATETIME,

   FailedLogins INT,

   Firewallrule VARCHAR(255)

);

 

CREATE INDEX IX_BlockedClient_LastFailedLogin ON BlockedClient(LastFailedLogin);

 

CREATE TABLE Config

(

   ConfigID INT NOT NULL PRIMARY KEY,

   ConfigDesc VARCHAR(255),

   ConfigValue INT,

);

 

CREATE TABLE Whitelist

(

   IPAddress VARCHAR(15) NOT NULL PRIMARY KEY,

   Description VARCHAR(255)

);

 

GO

 

Конфигурации

Есть три конфигурации, которые мы добавим в таблицу Config, чтобы задать некоторую пользовательскую конфигурацию. Я уверен, что могут быть реализованы многие другие конфигурации, но мы сосредоточимся на наиболее важных для системного администратора.

 

1) Время просмотра назад

Во-первых, нам нужна продолжительность времени, чтобы оглянуться назад с текущей метки времени для неудачных попыток входа в журнал событий. Оно должно быть довольно коротким, но при этом дольше, чем запланированный интервал для запуска сценария блока. Я устанавливаю продолжительность в секундах и делаю настройку на 15 минут назад.

Логика этого проста. Мы хотим отсканировать достаточно длительный период времени, чтобы захватить достаточно записей в журнале событий, но мы также хотим сократить его, если мы должны вручную удалить оттуда заблокированного клиента, который просто случайно ошибочно вводил пароль слишком много раз.  Скажем, приложение для подключения - это мобильное приложение, в которое пользователь вводит свое имя пользователя и пароль для подключения к базе данных. Он вводит неправильный пароль слишком много раз и блокируется, а затем, возможно, обращается в службу поддержки для сброса. Служба поддержки узнает о необходимости разблокировать IP-адрес, удалив запись из нашей таблицы BlockedClient, и к тому времени, когда она доведена до е сведения, прошлые неудачные события входа в журнал событий будут выведены из нашего окна времени сканирования, чтобы клиент не получал повторной блокировки. В этом случае мы не хотим использовать белый список своих IP-адресов; они получают IP динамически от своего мобильного провайдера, а некоторые вредоносные клиенты в будущем могут получить этот IP-адрес и обходить наше обнаружение атаки грубой силы.

 

INSERT INTO Config(ConfigID, ConfigDesc, ConfigValue)

VALUES(1, 'Time in seconds to look back for failed logins', 900)

 

2) Количество неудачных попыток входа в систему

Затем мы определяем порог неудачных попыток входа в систему до блокировки IP-адреса. Как правило, вы хотите, чтобы это было больше 1, чтобы случайный ошибочный пароль не вызывал блокировку клиента. Вероятно, оптимально что-то между 3 и 10, поскольку все, что выше - вероятно, вызвано злоумышленником. Мы установим значение на 3 попытки (то есть на 3-ей неудачной попытке, заблокируем IP-адрес клиента).

INSERT INTO Config(ConfigID, ConfigDesc, ConfigValue)

VALUES(2, 'Number of failed logins before client is blocked',

 

3) Время до разблокировки клиента

Мы не хотим блокировать данный IP-адрес навсегда. Клиенты часто подключаются с использованием динамического IP-адреса от своего интернет-провайдера или мобильного провайдера, поэтому есть вероятность, что IP-адрес, используемый злоумышленником, может использоваться доверенным клиентом в будущем. Такой сценарий маловероятен, но нам тоже нежелательно загромождать брандмауэр Windows постоянно растущим списком заблокированных IP-адресов. Пока вредоносный IP заблокирован на значительное количество времени, хакер или фальсификатор сценариев, нацеленный на наш сервер, сдастся и отступят после нескольких минут отклоненных подключений. Если они продолжат забивать наш сервер в течение длительного периода времени с одного и того же IP-адреса, у них будет только небольшое окно времени (частота, с которой выполняется блок-скрипт), чтобы сделать несколько попыток входа в систему, прежде чем снова заблокировать, значительно уменьшая эффективность их атаки.

Эта конфигурация находится в часах, и мы установим ее на 24; мы по-прежнему позволяем клиенту быть заблокированным навсегда, установив его на <= 0, если это желаемое поведение.

INSERT INTO Config(ConfigID, ConfigDesc, ConfigValue)

VALUES(3, 'Hours before client is unblocked (<=0 for never)', 24)

 

Управление правилами брандмауэра

Список заблокированных IP-адресов будет синхронизироваться с Windows Advanced Firewall с использованием триггеров для вставок и удалений в таблице BlockedClient. Триггер insert использует xp_cmdshell и netsh для автоматического добавления правил брандмауэра для каждого заблокированного IP. Затем запись в BlockedClient обновляется с именем правила брандмауэра.

CREATE TRIGGER trg_BlockedClient_I

  ON BlockedClient

  AFTER INSERT

AS

BEGIN

   SET NOCOUNT ON;

   DECLARE @IPAddress VARCHAR(15);

   DECLARE @FirewallRule VARCHAR(255);

   DECLARE @FirewallCmd VARCHAR(1000);

   DECLARE vINSERT_CURSOR CURSOR LOCAL FOR

     SELECT IPAddress FROM INSERTED

   

   OPEN vINSERT_CURSOR;

   FETCH NEXT FROM vINSERT_CURSOR INTO @IPAddress

 

   WHILE @@FETCH_STATUS = 0

   BEGIN

     SET @FirewallCmd = 'netsh advfirewall firewall add rule name="';

     SET @FirewallRule = 'SQL Server Failed Login Block for ' + @IPAddress;

   /*

       Alternative firewall rule that just blocks IP on SQL Server TCP port

       SET @FirewallCmd = @FirewallCmd + @FirewallRule + '" dir=in interface=any protocol=TCP action=block remoteip=' + @IPAddress + ' LocalPort=1433'        

       */

 

       SET @FirewallCmd = @FirewallCmd + @FirewallRule + '" dir=in interface=any action=block remoteip=' + @IPAddress

       

       EXEC xp_cmdshell @FirewallCmd --Create firewall rule

 

       UPDATE BlockedClient  --Update blocked client entry with firewall rule so we can reference for delete

       SET FirewallRule = @FirewallRule

       WHERE IPAddress = @IPAddress;

 

       FETCH NEXT FROM vINSERT_CURSOR INTO @IPAddress;

   END

 

   CLOSE vINSERT_CURSOR;

   DEALLOCATE vINSERT_CURSOR;

END

 

GO

Триггер delete ссылается на имя правила брандмауэра, сохраненного в BlockedClient, чтобы удалить правило из брандмауэра по имени, снова используя netsh.

CREATE TRIGGER trg_BlockedClient_D

  ON BlockedClient

  AFTER DELETE

AS

BEGIN

   SET NOCOUNT ON;

   DECLARE @FirewallRule VARCHAR(255);

   DECLARE @FirewallCmd VARCHAR(1000);

   DECLARE vDELETE_CURSOR CURSOR LOCAL FOR

     SELECT FirewallRule FROM DELETED

       WHERE FirewallRule IS NOT NULL

   

   OPEN vDELETE_CURSOR;

   FETCH NEXT FROM vDELETE_CURSOR INTO @FirewallRule

 

   WHILE @@FETCH_STATUS = 0

   BEGIN

       SET @FirewallCmd = 'netsh advfirewall firewall delete rule name="' + @FirewallRule + '" dir=in';

       

       EXEC xp_cmdshell @FirewallCmd --Delete firewall rule

 

       FETCH NEXT FROM vDELETE_CURSOR INTO @FirewallRule;

   END

 

   CLOSE vDELETE_CURSOR;

   DEALLOCATE vDELETE_CURSOR;

END

 

GO

Затем добавим простой триггер insert в белый список, чтобы автоматически удалять записи в BlockedClient, когда IP-адрес включен в белый список, если возникнет такая необходимость.

CREATE TRIGGER trg_WhiteList_I

  ON Whitelist

  AFTER INSERT

AS

BEGIN

   SET NOCOUNT ON; -- Automatically delete any blocked clients after they are whitelisted

   DELETE FROM BlockedClient

   WHERE EXISTS (SELECT * FROM INSERTED

                 WHERE INSERTED.IPAddress = BlockedClient.IPAddress);

END

 

GO

 

Размещение IP-адреса в белом списке

Прежде чем мы перейдем к коду, который фактически обнаруживает неудачные попытки входа в систему, давайте добавим некоторый код, чтобы упростить размещение IP-адреса в белом списке с использованием диапазонов CIDR (например, для белого списка наших подсетей LAN). Я позаимствовал некоторый код, чтобы упростить работу с нотификацией CIDR в T-SQL. Используя эти две функции, мы можем создать простую хранимую процедуру, которая может перечислить все IP-адреса в диапазоне CIDR и вставить в белый список.

 

Сперва - наши функции IP-помощника:

CREATE FUNCTION ConvertIPToLong(@IP VARCHAR(15))

RETURNS BIGINT

AS

BEGIN

   DECLARE @Long bigint

   SET @Long = CONVERT(bigint, PARSENAME(@IP, 4)) * 256 * 256 * 256 +

           CONVERT(bigint, PARSENAME(@IP, 3)) * 256 * 256 +

           CONVERT(bigint, PARSENAME(@IP, 2)) * 256 +

           CONVERT(bigint, PARSENAME(@IP, 1))

 

   RETURN (@Long)

END

 

GO

 

CREATE FUNCTION ConvertLongToIp(@IpLong bigint)

RETURNS VARCHAR(15)

AS

BEGIN

   DECLARE @IpHex varchar(8), @IpDotted  varchar(15)

 

   SELECT @IpHex = substring(convert(varchar(30), master.dbo.fn_varbintohexstr(@IpLong)), 11, 8)

 

   SELECT @IpDotted = convert(varchar(3), convert(int, _

                      (convert(varbinary, substring(@IpHex, 1, 2), 2)))) + '.' +

                      convert(varchar(3), convert(int, _

                      (convert(varbinary, substring(@IpHex, 3, 2), 2)))) + '.' +

                      convert(varchar(3), convert(int, _

                      (convert(varbinary, substring(@IpHex, 5, 2), 2)))) + '.' +

                      convert(varchar(3), convert(int, _

                      (convert(varbinary, substring(@IpHex, 7, 2), 2))))

 

   RETURN @IpDotted

END

Затем мы создаем один SP для вставки белых списков IP-адресов, которые полностью игнорируются нашим неудачным кодом регистрации входа в систему. Мы сделаем маску подсети дополнительным параметром, который по умолчанию равен / 32 для упрощения белого списка одиночных IP-адресов.

CREATE PROCEDURE WhitelistIP

(

   @IpAddress varchar(15),

   @Mask int = 32,

   @Description text = null

)

AS

BEGIN

   SET NOCOUNT ON;

   DECLARE @Base bigint  = cast(4294967295 as bigint);

   DECLARE @Power bigint  = Power(2.0, 32.0 - @Mask) - 1;

   DECLARE @LowRange bigint  = dbo.ConvertIPToLong(@IpAddress) & (@Base ^ @Power);

   DECLARE @HighRange bigint  = @LowRange + @Power;

   DECLARE @CurrentIP VARCHAR(15)

   IF @Description IS NULL

       SET @Description = 'Whitelist for ' + @IPAddress + '/' + CONVERT(varchar(2), @Mask);

 

   WHILE @LowRange <= @HighRange

   BEGIN

     SET @CurrentIP = dbo.ConvertLongToIp(@LowRange);

       INSERT INTO Whitelist(IPAddress, Description)

       SELECT @CurrentIP, @Description

       WHERE NOT EXISTS (SELECT * FROM Whitelist WHERE IPAddress = @CurrentIP);

 

       SET @LowRange = @LowRange + 1;

   END;

END

 

GO

Важно сразу же перечислить диапазоны IP-адресов в локальной сети, чтобы мы не блокировали себя, особенно если мы решили сделать так, чтобы созданные правила брандмауэра блокировали соединения на всех портах. Код для триггера_blackClient_I имеет команды для блокировки соединений на всех портах или только на TCP-порте, который слушает SQL Server. Если ваша подсеть локальной сети 192.168.1.x с маской подсети 255.255.255.0, запустите SP со следующими параметрами:

EXEC [dbo].[WhitelistIP] @IpAddress = N'192.168.1.0', @Mask = 24

Если вы добавляете в белый список IP-адреса, расположенные за пределами локальной сети, убедитесь, что это делается только для клиента с известным статическим IP-адресом от своего интернет-провайдера. С мобильным устройством или обычным домашним интернет-соединением общедоступный IP-адрес является динамическим, поэтому нет смысла в добавлении его в белый список. В общем, только внешние IP-адреса белого списка, которые вы знаете, всегда будут использоваться доверенными клиентами.

Блокировка вредоносных клиентов

Теперь давайте рассмотрим код, который фактически обнаруживает попытки взлома входа в систему и управляет нашими блочными списками и правилами брандмауэра. Я поместил этот код в одну хранимую процедуру, которую можно планировать на регулярной основе. Это можно сделать с помощью агента SQL Server или SQL Server CLI и планировщика заданий Windows (если используется версия Express, у которой нет агента SQL Server). Я рекомендую запускать это каждые 30 секунд до минуты. Убедитесь, что параметр «Время в секундах, чтобы посмотреть назад на неудавшиеся логины» в Config больше, этого времени, иначе появится окно неконтролируемого времени, когда неудачный вход в систему останется незамеченным.

Убедитесь, что учетная запись пользователя, запускающая сценарий, является локальным администратором на компьютере, на котором запущен SQL Server (если используется агент SQL Server, служба должна работать под учетной записью администратора локального компьютера). Доступ к машинным администраторам необходим для запуска команд netsh для добавления или удаления правил брандмауэра.

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
);

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 BlockedClient(IPAddress, LastFailedLogin, FailedLogins)
SELECT IPAddress,
MAX(LogDate) AS LastFailedLogin,
COUNT(*) AS FailedLogins
FROM
(
SELECT LogDate,
ProcessInfo,
Message,
ltrim(rtrim(substring(CONVERT(varchar(1000), Message), -- Extract client IP
charindex('[CLIENT: ', CONVERT(varchar(1000), Message)) + 9,
charindex(']', CONVERT(varchar(1000), Message)) - 9 - charindex('[CLIENT: ', CONVERT(varchar(1000), Message))))) as IPAddress
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
) AS t
WHERE NOT EXISTS (SELECT * FROM Whitelist l -- Check against whitelist
WHERE l.IPAddress = t.IPAddress)
AND NOT EXISTS (SELECT * FROM BlockedClient c -- ignore already blocked clients
WHERE c.IPAddress = t.IPAddress)
AND IPAddress <> '<local machine>' -- ignore failed logins from local machine
GROUP BY IPAddress
HAVING COUNT(*) >= @MaxFailedLogins -- Check against number of failed logins config
AND MAX(LogDate) >= COALESCE(@UnblockDate, MAX(LogDate)) -- Check that new entries meet delete config criteria so we don't unnecessarily
-- add a rule that would then get deleted.

DELETE FROM BlockedClient -- Delete entries older than the delete config
WHERE LastFailedLogin < @UnblockDate
END
GO

Вторая часть процедуры ищет клиентов, заблокированных дольше, чем 24-часовой период времени, который мы установили, и удаляет их, чтобы наш список не выходил из-под контроля.

Наконец, глядя на брандмауэр Windows, мы можем видеть наши правила блоков, созданные автоматически!



Примечание

Удаленный доступ к базе данных SQL Server всегда лучше всего использовать с помощью VPN для привлечения клиентов в частную локальную базу данных (и, при необходимости, с использованием доступа к учетной записи AD) для обеспечения максимальной безопасности. Эта настройка всегда обеспечивает максимальную безопасность и максимальную гибкость для управления доступом клиента.

Однако иногда разработчик или системный администратор вынуждены работать со слабо реализованной безопасностью, используя прямой доступ к порту прослушивания вне локальной сети и, что еще хуже, используя учетную запись sa. При такой настройке сервер, как правило, получает бомбардировку тысячами попыток входа в учетную запись SA с использованием общедоступных инструментов сканирования портов. Этот код обеспечивает бесплатную и легко развертываемую схему предотвращения вторжений, которая будет работать во всех версиях SQL Server за 2005 год, развернутых в среде Windows, будь то Express или один из платных корпоративных изданий.

No Comments

Add a Comment