Наверное, читатели хорошо помнят случай 25 января 2003 г., когда функционировавшие под управлением Microsoft SQL Server 2000 серверы по всему миру стали мишенями для коварного вируса SQL Slammer. Вирус SQL Slammer — это самовоспроизводящийся «червь», использующий ситуацию переполнения буферов на компьютерах с SQL Server. Зараженный сервер может впоследствии выполнять любые программы по указанию хакера.

К сожалению, жертвами той атаки стали не только серверы, функционировавшие под управлением SQL Server 2000. Компьютеры, на которых был запущен процессор Microsoft SQL Server Desktop Engine (MSDE) — бесплатно распространяемая версия процессора баз данных SQL Server 2000, — тоже оказались в опасности. В результате атака, которая казалась локальной и легко идентифицируемой, превратилась во всеобщее бедствие, поскольку MSDE представляет собой факультативный компонент, входящий в состав множества продуктов Microsoft и независимых поставщиков, таких как Microsoft Office XP Professional и Microsoft Visio 2002 Professional.

По умолчанию MSDE не устанавливается в этих продуктах для настольных систем, но как узнать, есть на машине данный компонент или нет? Один из вариантов — подготовить соответствующий сценарий. Любопытно, что, когда речь заходит о сценариях, администраторы часто рассматривают их как инструменты для ведения активных наступательных действий. К примеру, они считают, что сценарии годятся только для автоматизации типичных задач административного управления системами, таких как управление учетными записями пользователей, отслеживание ресурсов и управление конфигурацией. Между тем сценарии могут играть столь же важную роль в ситуациях, когда возникает необходимость реагировать на непредвиденный кризис, скажем, на появление вируса SQL Slammer. Чтобы проиллюстрировать эту мысль, я на примере атаки SQL Slammer покажу, как с помощью сценариев можно оценить масштабы ущерба и свести его к минимуму.

Понять суть кризиса

Написать сценарий, способный противостоять SQL Slammer или другой напасти, можно, только получив некоторое представление о самой проблеме. Нельзя же создавать сценарий, предусматривающий закрытие портов, ликвидацию процессов, отключение служб, замену файлов или выполнение каких-либо иных задач, ничего не зная при этом об угрозе, которую необходимо предотвратить. Когда администратор возьмется за идентификацию проблемы и начнет искать пути ее решения, любая информация, которую ему удастся получить в координационном центре CERT (CERT Coordination Center, CERT/CC, http://www.cert.orf), у поставщика, в процессе наблюдения за сетью или из других источников, может оказаться полезной. В частности, об SQL Slammer администраторы смогли получить следующую информацию.

  • «Червь» способен заражать все версии SQL Server 2000 Service Pack 2 (SP2) с неустановленными модулями коррекции и более ранние, включая все версии MSDE SP2.
  • «Червь» использует известную проблему переполнения буферов, обнаруженную в июле 2002 г., о чем сразу же была проинформирована организация CERT. Тогда же, в июле 2002 г., Microsoft выпустила соответствующий модуль коррекции, однако на многих системах (и прежде всего на тех машинах, где функционирует процессор MSDE) этот модуль так и не был установлен, по двум основным причинам. Во-первых, в ряде случаев процедура установки была слишком сложной (в частности, установка старого варианта модуля требовала выполнения слишком большого числа операций вручную). И во-вторых, многие администраторы не осознавали масштабов возможных проблем, а последние определяются числом имеющихся в сети компьютеров, где установлен MSDE.
  • «Червь» выбирает в качестве мишени службу SQL Server Resolution Service, которая прослушивает UDP-порт 1434 (в выходном файле программы netstat.exe этот порт именуется ms-sql-m).
  • После заражения компьютера вирусом SQL Slammer находящийся в памяти резидентный модуль пытается воспроизводить себя, направляя на UDP-порт 1434 по произвольным адресам IP сообщения размером 376 байт.

Итак, выяснение масштабов проблемы было одним из наиболее сложных аспектов борьбы с вирусом SQL Slammer. Именно поэтому специалисты Microsoft немедленно взялись за разработку средств выявления компьютеров, на которых запущены SQL Server 2000 и MSDE. А можно ли возложить эту функцию на сценарий? Разумеется, да! Для обнаружения входящих в «группу риска» компьютеров можно использовать такой, например, сценарий, как IdentifySQLComputer.vbs, а затем остановить дальнейшее распространение вируса с помощью сценария, подобного моему DisableSQL Service.vbs.

Оценка кризиса

Сценарий IdentifySQLComputers.vbs, показанный в листинге 1, в значительной степени строится на серии сценариев, о которых я рассказал в опубликованной в феврале 2003 г. статье «Удаленное администрирование с помощью WMI» (см. Windows & .NET Magazine/RE, № 2 за 2003 год). Его назначение достаточно простое. Основываясь на данных текстового файла ввода, где хранится список имен компьютеров, сценарий с помощью функций Windows Management Instrumentation (WMI) определяет, установлены ли на целевых машинах пакеты SQL Server 2000 или MSDE. Но поскольку выполнение сценария IdentifySQLComputers.vbs связано с использованием WMI, он может функционировать лишь в том случае, если служба WMI установлена и на целевых компьютерах. В Windows 2000 и в более поздних версиях эта инфраструктура входит в число основных компонентов системы, то есть она установлена и запущена. Но если в сети есть компьютеры, функционирующие под Windows NT 4.0 или Windows 98, сценарий IdentifySQLComputers.vbs можно выполнять лишь после того, как WMI будет установлена и сконфигурирована на этих машинах.

В каждой строке файла ввода Computers.txt должно содержаться одно имя компьютера. Для каждого внесенного в файл компьютера сценарий отображает сообщение одного из трех типов:

  • GOOD означает, что ни SQL Server 2000, ни MSDE на данной целевой машине не установлены;
  • BAD означает, что на данной целевой машине установлен пакет SQL Server 2000 либо MSDE;
  • ERROR означает, что попытка установления связи с целевым компьютером закончилась неудачей. Это может быть вызвано несколькими причинами, например в файле ввода неверно указано имя компьютера, неисправны средства подключения к сети (машина не ответила на запрос) или на компьютер нет WMI.

А сейчас я расскажу о том, как функционирует сценарий IdentifySQL Computers.vbs. Как показано в листинге 1, в начале сценария я определил константу ForReading и инициализировал две переменные: strInputFile и strServiceName. Значение переменной strInputFile указывает на файл ввода Computers.txt. Значение переменной strServiceName — это внутреннее имя (т. е. имя, используемое операционной системой при выполнении внутренних процедур) той службы, которая проверяется. В данном случае я хочу выяснить, активизирована ли на персональных компьютерах служба SQL Server, поскольку она запускает службу SQL Server Resolution Service, а последняя как раз и является мишенью SQL Slammer. Внутреннее имя службы SQL Server — MSSQLSERVER. Внутреннее имя службы указано в поле Service name страницы General Properties соответствующей службы в оснастке Services консоли управления Microsoft Management Console, MMC.

Далее я использую метод OpenTextFile объекта FileSystem Object для того, чтобы открыть файл Computers.txt. Функция OpenTextFile возвращает ссылку на объект TextStream, который я указываю в качестве значения переменной objTextStream. Чтобы обратиться к содержимому файла, я использую метод ReadAll объекта TextStream; этот метод считывает в память весь файл ввода. Затем я вызываю функцию Split языка VBScript и использую в качестве разделителя символ возврата каретки / перевода строки (его представляет переменная vbCrLf). Функция Split расчленяет введенный в память файл и возвращает массив, содержащий имена компьютеров. Я указываю этот массив в качестве значения переменной массива arrComputers и закрываю файл ввода.

Далее я ввожу ссылку на поток StdOut объекта WScript. Без этого кода можно обойтись; я задействую его только для удобства. Указав ссылку именно таким образом, я получаю возможность пересылать тексты на консоль с использованием более компактной системы обозначений StdOut.WriteLine вместо относительно подробной системы WScript.StdOut.WriteLine. Если применяется свойство StdOut, то необходимо запускать сценарий с хостом cscript.exe, т. е. wscript.exe в этом случае не подходит, ибо «не понимает» потоков STDIO (а именно STDIN, STDOUT и STDERR).

Указав ссылку на переменную StdOut, я создаю объект WshShell. Позднее я воспользуюсь методом Exec данного объекта, чтобы проверить наличие и готовность каждого компьютера перед тем, как попытаться установить с ним связь. Применение метода Exec накладывает на использование данного сценария еще одно ограничение. Дело в том, что специалисты Microsoft наделили методом Exec систему Windows Script Host (WSH) 5.6, а значит, данный сценарий будет выполняться лишь на тех компьютерах, где установлен пакет WSH 5.6. В системы Windows Server 2003 и Windows XP эта программа входит по умолчанию. Но если планируется выполнять сценарий на компьютерах Windows 2000, Windows NT 4.0 или Windows 98, программу придется модернизировать до уровня WSH 5.6.

Далее для выполнения основного объема работ я использую оператор For Each... Next (иначе именуемый циклом For Each). Перед тем как начать цикл, я активизирую реализованный в языке VBScript механизм обработки ошибок, оператор On Error Resume Next. Этот оператор действует в полном соответствии со своим названием: если на этапе выполнения сценария возникает ошибка (ситуация On Error), сценарий не прекращает свою работу, а продолжает после выполнения следующей команды (оператор Resume Next). Применение оператора On Error Resume Next позволяет мне задействовать объект Err языка VBScript с тем, чтобы с легкостью выявлять и обрабатывать ошибки.

Цикл For Each повторяется для каждого элемента созданного мною ранее массива arrComputers. Применительно к каждому компьютеру, упомянутому во входном файле, сценарий выполняет три операции.

Операция 1. Определение готовности компьютера. Чтобы определить готовность целевого компьютера, сценарий направляет ему запрос с помощью метода Exec объекта WshShell, как показано в метке A листинга 1. Метод Exec возвращает ссылку на объект WshScriptExec, который используется сценарием для считывания и захвата данных, полученных в результате выполнения команды Ping. С помощью реализованной в VBScript функции LCase я преобразую прописные буквы в этих данных в строчные и сохраняю полученную информацию в переменной с именем strPingResults.

Для оценки хранящихся в переменной strPingResults данных в сценарии предусмотрены оператор If... Then...Else, а также функция InStr языка VBScript. Если переменная strPingResults содержит подстроку replyfrom, значит, проверка готовности дала положительный результат, и сценарий переходит к выполнению кода, указанного в метке B листинга 1. Если же переменная strPingResults не содержит подстроки replyfrom, значит, компьютер не ответил на запрос о готовности, и сценарий переходит к выполнению команды, следующей за оператором Else (показана в метке C листинга 1). Эта команда направляет на консоль сообщение об ошибке [Ping failed].

Операция 2. Соединение с компьютером. Для соединения с WMI целевого компьютера сценарий использует функцию GetObject языка VBScript в сочетании со стандартной строкой подключения WMI (иначе именуемой моникером), как показано в программном блоке в метке B. Если не совсем понятно, что указывается в строке подключения WMI, рекомендую прочитать статью «WMI-моникеры», опубликованную по адресу http://www.osp.ru/win2000/worknt/701.htm. GetObject возвращает ссылку на объект SWbemServices библиотеки WMI Scripting Library, который представляет аутентифицированное соединение с WMI на локальном или удаленном компьютере. Переменная с именем objWMIService инициализируется ссылкой SWBemServices, возвращаемой методом GetObject.

По завершении попытки установить соединение сценарий с помощью оператора If...Then...Else и свойства Number объекта Err определяет, было ли соединение успешным. Если значение Err.Number отлично от нуля, значит, WMI-соединение не состоялось, и сценарий направляет на консоль соответствующее сообщение об ошибке. Если же значение Err.Number равно нулю, соединение было успешным, и сценарий переходит к оператору Else второго цикла If...Then...Else.

Операция 3. Восстановление службы. С помощью ссылки на объект SWvemServices (objWMIService) библиотеки WMI Scripting Library сценарий восстанавливает целевую службу. Точнее говоря, сценарий использует метод Get объекта SWbemServices, который принимает три факультативных параметра: путь доступа к объекту, некоторые флажки и именованный набор значений. Для данного сценария необходим только один параметр. Параметр доступа к объекту, который сценарий передает методу Get, принимает следующую форму:

Get «Class.Key=?Value?»

где Class означает класс WMI, который моделирует целевой ресурс, Key — это свойство класса, идентифицирующее уникальные экземпляры управляемого ресурса, а ?Value? — значение, уникально идентифицирующее экземпляр ресурса, который должна восстановить инфраструктура WMI. Допустим, мы имеем следующий маршрут доступа к объекту:

«Win32_Service.Name=
?MSSQLSERVER?»

Win32_Service — это класс, Name — свойство Key класса Win32_Service, а ?MSSQLSERVER? — это уникальный экземпляр класса Win32-Service, который мы хотим восстановить с помощью метода Get. Единственное отличие настоящего демонстрационного пути доступа от пути доступа в сценарии IdentifySQLComputers.vbs состоит в том, что в последнем сценарии имя целевого экземпляра хранится в переменной strServiceName. Если в сценарии имя целевого экземпляра хранится в переменной, такой сценарий легко адаптировать в случае, если потребуется задействовать его для восстановления других служб.

Возможно, вы заметили, что в упомянутых путях доступа к объектам я заключал значение в одинарные кавычки. Это значение нужно заключать либо в одинарные, либо в двойные кавычки. В кодах VBScript я обычно использую одинарные кавычки, так как с ними легче работать. Например, использование вложенных одинарных кавычек допускается, тогда как конструкций с вложенными двойными кавычками следует избегать.

После вызова метода Get сценарий в третий раз использует оператор If...Then...Else и свойство Number объекта Err для того, чтобы выяснить результат применения упомянутого метода. Если метод Get не дает результата (что в данном случае хорошо), то значение Err.Number не будет равно нулю, а значит, служба MSSQLSERVER на целевом компьютере не установлена. Если же метод Get дает результат, значит, служба MS-SQLSERVER установлена на целевом компьютере. В обоих случаях сценарий направляет на консоль соответствующее сообщение.

На экране 1 показаны результаты выполнения сценария IdenrigySQL Computers.vbs в моей лаборатории. Из семи компьютеров, перечисленных в выходных данных, под подозрением оказалась только одна машина (ее имя выделено). Если бы я не знал о том, что компьютеры с именами dellsx260a и wdywtgt не были подключены к сети, а устройство dellxps — это компьютер Windows XP Home Edition, не допускающий установления соединений с удаленными системами WMI, мне пришлось бы выяснять, почему с этих машин пришли сообщения об ошибках. Но в данном случае ошибки были преднамеренными.

Чтобы применить сценарий IdentifySQLComputers.vgs в конкретной сети, достаточно просто создать текстовый файл со списком функционирующих в ней целевых компьютеров. Чтобы избежать необходимости модифицировать сценарий, нужно присвоить текстовому файлу имя Computers.txt и сохранить его в каталоге C:Scripts. Иначе придется изменять значения, присвоенные переменной strInputFile.

А вот «вопрос на засыпку»: можно ли в сценарии в качестве источника имен компьютеров вместо текстового файла использовать Active Directory (AD)? Да, можно. И изменения придется внести совсем незначительные. Более подробно об этих изменениях рассказано во врезке «Получение имен компьютеров из каталога AD».

Сдерживание кризиса

Теперь, когда мы определили, на каких компьютерах установлены SQL Server 23000 и MSDE, пора заняться подавлением кризиса. Для этого нужно отключить службу MSSQLSERVER на тех компьютерах, которые сценарий IdentifySQLComputers.vbs охарактеризовал как BAD. Остановка данной службы поможет нам ограничить воспроизводство «червя» и выиграть время для того, чтобы продумать меры по ликвидации вируса и возвращению в строй зараженных машин.

К этому моменту уже написана значительная часть сценария отключения DisableSQLService.vbs, представленного в листинге 2, так как коды сценариев DisableSQLService.vbs и IdentifySQLComputers.vbs во многом совпадают. Остается только добавить логику остановки службы и указать в качестве типа запуска службы режимы Manual или Disabled.

В метке A листинга 2 представлен программный блок, прекращающий функционирование службы. Вначале я с помощью свойства AcceptStop службы Win32_Service определяю, выполняется ли целевая служба. Если AcceptStop возвращает значение TRUE, я прекращаю работу службы с помощью метода StopService службы Win32_Service. Затем я использую возвращаемое данным методом значение для того, чтобы определить, было ли применение метода StopService успешным, и направляю соответствующее извещение на консоль.

Как показано в метке B листинга 2, чтобы выяснить, избран ли в качестве типа запуска целевой службы режим Disabled, я задействую свойство StartMode службы Win32_Service. Если же задан другой режим, я указываю его с помощью метода Win32_Service ChangeStartMode. Чтобы выяснить, было ли применение метода ChangeStartMode успешным, я использую возвращаемое методом значение и направляю соответствующее сообщение на консоль.

Я выполнил сценарий Disable SQLService.vbs применительно к тому же набору компьютеров, что и сценарий Identify SQLComputers.vbs. Как показано на экране 2, сценарий DisableSQLService.vbs успешно прекратил выполнение службы MSSQLSERVER и отключил ее на компьютере tmtowtdi.

Не «если», но «когда»

Рано или поздно какой-нибудь изнывающий от безделья оболтус напишет следующий вирус — с этим, к сожалению, ничего не поделаешь. Как мы видим на примере программ IdentifySQLComputers.vbs и DisableSQLService.vbs, сценарии могут быть мощными инструментами для оценки и сдерживания кризиса до тех пор, пока поставщик не найдет управу на новый вирус. И если не пожалеть времени на то, чтобы научиться писать такие сценарии, вы сможете реагировать на новые угрозы гораздо быстрее и эффективнее.


Получение имен компьютеров из каталога AD

При вводе в сценарий имен компьютеров можно обойтись без входного текстового файла, используя в качестве источника каталог Active Directory (AD). Изменения, которые для этого необходимо внести в код, на удивление незначительны. В сценарии IdentifySQLComputers2.vbs (см. листинг А) представлены модификации, превращающие сценарий на базе текстового файла IdentifySQLComputers.vbs в сценарий на основе каталога AD. Рассмотрим, чем код IdentifySQLComputers2.vbs отличается от кода IdentifySQLComputers.vbs.

В начале сценария вместо переменной strInputFile я ввел новую переменную с именем strComputerContainer, как показано в метке A. Переменная strComputerContainer представляет отличительное имя (distinguished name, DN) контейнера AD, в котором хранятся объекты «учетная запись компьютера». Кстати, нужно иметь в виду, что если объекты «компьютер» хранятся в организационных единицах (Organizational Unit, OU), то имена контейнеров будут начинаться не с символов «cn=», а с символов «ou=».

В метке B я выполняю подключение к целевому контейнеру в каталоге AD. Для этого я использую функцию GetObject языка VBScript и интерфейсы ADSI (Active Directory Service Interfaces). После установления соединения я применяю к контейнеру фильтр, чтобы ограничить формируемый далее список объектами типа Computer. Использовать данный фильтр необязательно, но эта мера может оказаться полезной, так как, если контейнер содержит объекты, не относящиеся к типу Computer, они не будут перечислены в списке.

С помощью оператора For Each...Next (иначе именуемого циклом For Each) я формирую список содержимого целевого контейнера (см. метку C). Данный код несколько отличается от соответствующего фрагмента кода IdentifySQLComputers.vbs. В сценарии IdentifySQLComputers.vbs осуществляется перебор строк массива. В данном же случае контейнер objContainer содержит объекты AD типа Computer, поэтому мне приходится извлекать имя каждого объекта Computer.

Для получения имен объектов Computer я использую Name, вспомогательное свойство интерфейса ADSI, которым обладает любой объект AD. Свойство Name возвращает относительное отличительное имя объекта (Relative Distinguished Name, RDN). Имена RDN форматируются как пары ключ=значение (например, cn=Dell220), поэтому для того, чтобы использовать имя в сценарии, нужно предварительно удалить его первую часть (ключ=). Для вычленения фрагмента имени RDN, следующего за знаком равенства, я использую функцию Split языка VBScript. Я присоединяю значение (1) к концу оператора Split, тем самым указывая ему на необходимость возвращения второго элемента имени (в данном случае — Dell220). Полученная строка хранится в переменной strComputer.

Вот и все изменения, которые нужно внести в код. В остальном IdentifySQLCompretrs2.vbs ничем не отличается от сценария IdentifySQLCompretrs.vbs.

Администратор может не только хранить имена компьютеров в текстовом файле или извлекать их из каталога AD, но и указывать диапазон IP-адресов. О том, как это делается, рассказано в статье «Удаленное администрирование с помощью WMI».

Боб Уэллс (bobwells@winntmag.com) - Консультант по программному обеспечению; специализируется на вопросах проектирования и реализации инфрастуктуры информационных систем, основанных на NT. Имеет сертификаты MCSE и MCT