Обнаружение брут форс атаки SQL Server: часть 1
При развертывании базы данных 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 или один из платных корпоративных изданий.