Kaspersky Security Center — борьба за автоматизацию

от автора

Как это ни странно, я нашёл на Хабре всего одну статью по данной тематике — и ту в песочнице и сильно незаконченную фактически содержащую в себе маленький кусочек чуть переделанной справки по продукту. Да и Google по запросу klakaut молчит.

Я не собираюсь рассказывать, как администрировать иерархию Kaspersky Security Center (далее по тексту KSC) из командной строки — мне это пока не понадобилось ни разу. Просто хочу поделиться некоторыми соображениями по поводу средств автоматизации с теми, кому это может понадобиться, и разберу один кейс, с которым мне пришлось столкнуться. Если тебе, %habrauser%, эта тема будет интересной — добро пожаловать под кат.

Исторически сложилось так, что в качестве средства антивирусной защиты на работе я предпочитаю продукты Лаборатории Касперского (далее ЛК). Причины и прочие священные войны личных мнений, пожалуй, оставим за кадром.

Естественно, хотелось бы централизованно развернуть, защитить, оградить и не пущать рисовать красивые графики, интегрироваться в существующие системы мониторинга и заниматься прочим перекладыванием работы с больной головы на здоровый сервер. И если с развёртыванием и защитой тут всё более или менее в порядке (у ЛК даже есть какие-то онлайн-курсы по продуктам), то с интеграцией уже сильно грустнее: в последней на текущий момент версии KSC 10.2.434 появилась интеграция аж с двумя SIEM: Arcsight и Qradar. На этом всё.

Для интеграции в что-то своё KSC предоставляет аж 2 интерфейса:

  • klakdb: в БД KSC есть ряд представлений с именами, начинающимися на «v_akpub_», из которых можно достать какую-то информацию о состоянии антивирусной защиты.
  • klakaut: DCOM-объект, позволяющий скриптовать работу с KSC.

По обоим пунктам есть документация в составе KSC, как это и указано в статьях, на которые я дал ссылки. Правда, документация эта вызывает ряд вопросов, которые можно задать в службу поддержки корпоративных продуктов CompanyAccount и получить ответ вида «Уточнили информацию. К сожалению, поддержка по скриптам для klakaut не оказывается.».

Минусы klakdb очевидны: чтобы напрямую обратиться к БД, нужно иметь к этой БД доступ, что приводит к необходимости лишних телодвижений по созданию правил доступа в межсетевых экранах, настройке прав доступа на серверах СУБД и прочему крайне неувлекательному времяпрепровождению. Ну и плюс мониторинг актуальности всех этих правил, естественно. Особенно интересно становится, когда имеется 20+ серверов — и все в разных филиалах, в каждом из которых свои администраторы.

Ну и вишенки на этом торте: доступ исключительно Read Only и, мягко говоря, неполная информация о среде. Плюсы не столь очевидны, но тоже есть: можно очень быстро выгрузить по одному серверу статистическую информацию по количеству хостов, используемым версиям антивирусов и антивирусных баз, а главное — можно весьма удобно (удобство зависит от знания SQL) работать с зарегистрированными на KSC событиями антивирусной инфраструктуры.

klakaut в этом плане значительно более интересен: подключившись к корневому серверу иерархии, можно средствами самого KSC пройтись по оной иерархии и получить доступ ко всем нужным данным. Например, построить дерево серверов KSC с пометкой, кто из них живой, а кто нет, позапускать задачи, поперемещать компьютеры и вообще дать волю фантазии.

Минусы тоже есть, естественно: долго и сложно. Если нужно собрать какую-то статистику — нужно будет сначала долго писать скрипт, а потом долго ловить баги ждать, когда он отработает.

Естественно, никто не запрещает (по крайней мере, мне про это неизвестно) использовать оба механизма вместе: например, пройтись по иерархии серверов с помощью klakaut, получить полный список серверов KSC с информацией об используемых БД, а потом уже передать эту информацию в более другие средства автоматизации, которые удалят устаревшие правила из сетевых экранов, создадут новые, дадут разрешения на доступ и принесут кофе в постель отредактируют список источников данных в вашей системе мониторинга, которая, в свою очередь, опросит список и, обнаружив какие-нибудь девиации, с помощью klakaut сделает что-нибудь хорошее. Ну, или просто зарегистрирует инцидент в трекере. Тогда что-нибудь хорошее сделают администраторы в ручном режиме.

Воодушевлённый всеми этими соображениями, я написал свой первый скрипт:

Вот он

 $Params = New-Object -ComObject 'klakaut.KlAkParams'; $Params.Add('Address', 'localhost:13000'); $Params.Add('UseSSL', $true);  $Proxy = New-Object -ComObject 'klakaut.KlAkProxy'; try {     $Proxy.Connect($Params);     $Proxy.Disconnect(); } catch {     $_; }  Remove-Variable -Name 'Params'; Remove-Variable -Name 'Proxy'; 

И запустил его на сервере:

 Exception calling "Connect" with "1" argument(s): "Transport level error while connecting to http://localhost:13000: authentication failure" At line:7 char:5 +     $Proxy.Connect($Params); +     ~~~~~~~~~~~~~~~~~~~~~~~     + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException     + FullyQualifiedErrorId : ComMethodTargetInvocation 


Начало хорошее.

Хозяйке на заметку

Если использовать js, эта ошибка не возникает. Интересно, почему.

Открыв консоль KSC, я убедился, что с правами у меня всё в порядке.

К сожалению, KSC не логирует неудачные попытки входа. Переписка с вендором показала, что логирование неудачных попыток входа можно включить (это была отдельная увлекательная история, которая, кстати, ещё не закончилась), однако данная конкретная попытка в логи всё равно попадать отказалась.

Казалось бы, можно сделать вот так:

Неправильный скрипт

 $Params = New-Object -ComObject 'klakaut.KlAkParams'; $Params.Add('Address', 'localhost:13000'); $Params.Add('UseSSL', $true);  #------------ Зададим в явном виде данные для входа -------------------------------- $Params.Add('User', 'kavadmin'); $Params.Add('Password', 'P@ssw0rd'); $Params.Add('Domain', 'test'); #-----------------------------------------------------------------------------------  $Proxy = New-Object -ComObject 'klakaut.KlAkProxy'; try {     $Proxy.Connect($Params);     $Proxy.Disconnect(); }  Remove-Variable -Name 'Params'; Remove-Variable -Name 'Proxy'; 

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

Необходимо выставить в настройках COM, на вкладке Default Properties:
Default Authentication Level: Packet
Default Impersonation Level: Delegate

Эта инструкция заставила работать исходный скрипт, но показалась мне сомнительной в плане безопасности, поэтому я решил покопать чуть глубже. После некоторого времени поисков нашёлся добрый человек, который подсказал мне, как задать указанные уровни проверки подлинности и олицетворения для конкретного объекта, а не разрешать всем и всё сразу:

Правильный скрипт

 $Params = New-Object -ComObject 'klakaut.KlAkParams'; $Params.Add('Address', 'localhost:13000'); $Params.Add('UseSSL', $true);  $Proxy = New-Object -ComObject 'klakaut.KlAkProxy';  $code =  @" using System; using System.Runtime.InteropServices;  public class PowershellComSecurity {    [DllImport("Ole32.dll", CharSet = CharSet.Auto)]    public static extern int CoSetProxyBlanket(IntPtr p0, uint p1, uint p2, uint p3, uint p4, uint p5, IntPtr p6, uint p7);     public static int EnableImpersonation(object objDCOM) { return CoSetProxyBlanket(Marshal.GetIDispatchForObject(objDCOM), 10, 0, 0, 0, 3, IntPtr.Zero, 0); } } "@ Add-Type -TypeDefinition $code; Remove-Variable -Name 'code';  [PowershellComSecurity]::EnableImpersonation($Proxy) | Out-Null;  try {     $Proxy.Connect($Params);     # <-- Вот сюда мы будем вставлять код     $Proxy.Disconnect(); } catch {     $_; }  Remove-Variable -Name 'Params'; Remove-Variable -Name 'Proxy'; 

Вот так скрипт никаких ошибок выдавать не стал. Первый квест пройден.

Хозяйке на заметку

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

Следующая задача: получить от KSC данные об используемой БД.

А вот тут всё сложно: документация о том, как это сделать, молчит. Исследование класса KlAkProxy ничего интересного не выявило, кроме параметра KLADMSRV_SERVER_HOSTNAME, который оказался идентификатором компьютера, на котором установлен KSC.

Перейдём тогда к компьютеру, для этого есть специальный класс KlAkHosts2. Для сокращения количества кода приведу только содержимое блока try:

блок try

 try {     $Proxy.Connect($Params);     $KSCHost = New-Object -ComObject 'klakaut.KlAkHosts2';     $KSCHost.AdmServer = $Proxy;     $HostParams = New-Object -ComObject 'klakaut.KlAkCollection';     $HostParams.SetSize(1);     $HostParams.SetAt(0, 'KLHST_WKS_DN');     ($KSCHost.GetHostInfo($Proxy.GetProp('KLADMSRV_SERVER_HOSTNAME'), $HostParams)).Item('KLHST_WKS_DN');     Remove-Variable -Name 'HostParams';     Remove-Variable -Name 'KSCHost';     $Proxy.Disconnect(); } 

Обратите внимание: переменная $Params, которую я использовал при подключении к KSC — экземпляр класса KlAkParams. А переменная $HostParams при, на мой взгляд, аналогичной функциональности, является экземпляром класса KlAkCollection. Почему используются разные классы — боюсь даже представить. Видимо, то, что SetAt принимает первым аргументом только целочисленные значения — очень принципиальный момент.

Данный код вернул значение «KSC», а значит, я на верном пути.

Метод GetHostInfo класса KlAkHosts2 достаточно хорошо задокументирован, но — не содержит нужной мне информации. Увы и ах. Зато есть метод GetHostSettings. Всё описание для которого сводится к следующему:

Returns host’s settings as setting storage.

Давайте, заглянем внутрь:

try

 try {     $Proxy.Connect($Params);     $KSCHost = New-Object -ComObject 'klakaut.KlAkHosts2';     $KSCHost.AdmServer = $Proxy;     $KSCSettings = $KSCHost.GetHostSettings($Proxy.GetProp('KLADMSRV_SERVER_HOSTNAME'), 'SS_SETTINGS');     $KSCSettings.Enum() | % {         '------------------------';         $tmp = $_;         $tmp | % {"$_ = $($tmp.Item($_))";};         Remove-Variable -Name 'tmp';     };     Remove-Variable -Name 'KSCSettings';     Remove-Variable -Name 'KSCHost';     $Proxy.Disconnect(); } 

Результат

 ------------------------ PRODUCT = .core SECTION = SubscriptionData VERSION = .independent ------------------------ PRODUCT = 1093 SECTION = 85 VERSION = 1.0.0.0 ------------------------ PRODUCT = 1093 SECTION = 87 VERSION = 1.0.0.0 ------------------------ PRODUCT = 1093 SECTION = KLEVP_NF_SECTION VERSION = 1.0.0.0 ------------------------ PRODUCT = 1093 SECTION = KLNAG_SECTION_DPNS VERSION = 1.0.0.0 ------------------------ PRODUCT = 1093 SECTION = KLSRV_CONSRVINIT VERSION = 1.0.0.0 ------------------------ PRODUCT = 1093 SECTION = KLSRV_CONSRVUPGRADE VERSION = 1.0.0.0 ------------------------ PRODUCT = 1093 SECTION = KLSRV_DEF_NAGENT_PACKAGE VERSION = 1.0.0.0 ------------------------ PRODUCT = 1093 SECTION = KLSRV_MASTER_SRV VERSION = 1.0.0.0 ------------------------ PRODUCT = 1093 SECTION = KLSRV_NETSIZE_SECTION VERSION = 1.0.0.0 ------------------------ PRODUCT = 1093 SECTION = KLSRV_PKG_ANDROID_CERT_SECTION VERSION = 1.0.0.0 ------------------------ PRODUCT = 1093 SECTION = KLSRV_PROXY_SECTION VERSION = 1.0.0.0 ------------------------ PRODUCT = 1093 SECTION = KLSRV_SRVLIC_SECTION VERSION = 1.0.0.0 ------------------------ PRODUCT = 1093 SECTION = KLSRV_USER_ACCOUNTS_SECTION VERSION = 1.0.0.0 ------------------------ PRODUCT = 1093 SECTION = KSNPROXY_KEY_STORAGE VERSION = 1.0.0.0 ------------------------ PRODUCT = 1093 SECTION = KSNPROXY_SETTINGS VERSION = 1.0.0.0 ------------------------ PRODUCT = 1093 SECTION = Packages VERSION = 1.0.0.0 ------------------------ PRODUCT = 1093 SECTION = Updater VERSION = 1.0.0.0 ------------------------ PRODUCT = 1103 SECTION = 85 VERSION = 1.0.0.0 ------------------------ PRODUCT = 1103 SECTION = 86 VERSION = 1.0.0.0 ------------------------ PRODUCT = 1103 SECTION = FileTransfer VERSION = 1.0.0.0 ------------------------ PRODUCT = 1103 SECTION = KLEVP_NF_SECTION VERSION = 1.0.0.0 ------------------------ PRODUCT = 1103 SECTION = KLNAG_KLNLA_DATA VERSION = 1.0.0.0 ------------------------ PRODUCT = 1103 SECTION = KLNAG_SECTION_NETSCAN VERSION = 1.0.0.0 ------------------------ PRODUCT = 1103 SECTION = KLNAG_SECTION_SERVERDATA VERSION = 1.0.0.0 ------------------------ PRODUCT = 1103 SECTION = Updater VERSION = 1.0.0.0 ------------------------ PRODUCT = KAVFSEE SECTION = .KLNAG_SECTION_REBOOT_REQUEST VERSION = 8.0.0.0 ------------------------ PRODUCT = KAVFSEE SECTION = 85 VERSION = 8.0.0.0 ------------------------ PRODUCT = KAVFSEE SECTION = Backup section VERSION = 8.0.0.0 ------------------------ PRODUCT = KAVFSEE SECTION = Business logic section VERSION = 8.0.0.0 ------------------------ PRODUCT = KAVFSEE SECTION = HSM system section VERSION = 8.0.0.0 ------------------------ PRODUCT = KAVFSEE SECTION = Internal product info VERSION = 8.0.0.0 ------------------------ PRODUCT = KAVFSEE SECTION = KLEVP_NF_SECTION VERSION = 8.0.0.0 ------------------------ PRODUCT = KAVFSEE SECTION = Notification section VERSION = 8.0.0.0 ------------------------ PRODUCT = KAVFSEE SECTION = Predefined tasks section VERSION = 8.0.0.0 ------------------------ PRODUCT = KAVFSEE SECTION = Quarantine section VERSION = 8.0.0.0 ------------------------ PRODUCT = KAVFSEE SECTION = Reporting section VERSION = 8.0.0.0 ------------------------ PRODUCT = KAVFSEE SECTION = Trusted processes section VERSION = 8.0.0.0 

В klakaut.chm есть раздел «List of KLHST_WKS_PRODUCT_NAME and KLHST_WKS_PRODUCT_VERSION values for products», где можно подсмотреть, что поле PRODUCT для KSC должно быть 1093, соответственно, всё остальное можно смело проигнорировать. Пока что, по крайней мере.

Пробежавшись глазами по названиям секций, я решил просмотреть 85 и 87, поскольку остальные на нужное мне были не очень похожи.

try

 try {     $Proxy.Connect($Params);     $KSCHost = New-Object -ComObject 'klakaut.KlAkHosts2';     $KSCHost.AdmServer = $Proxy;     $KSCSettings = $KSCHost.GetHostSettings($Proxy.GetProp('KLADMSRV_SERVER_HOSTNAME'), 'SS_SETTINGS');     $KSCSettings.Read('1093', '1.0.0.0', '85');     '-----------------';     $KSCSettings.Read('1093', '1.0.0.0', '87');     Remove-Variable -Name 'KSCSettings';     Remove-Variable -Name 'KSCHost';     $Proxy.Disconnect(); } 

Результат

 EventFolder EventStoragePath KLAG_WAIT_SCHED_FOR_START_EVENT TaskStoragePath ----------------- KLSRV_AD_SCAN_ENABLED KLSRV_CONNECTION_DATA KLSRV_DATABASENAME KLSRV_NET_SCAN_ENABLED KLSRV_SERVERINSTANCENAME KLSRV_SP_DPNS_ENABLE KLSRV_SP_FASTUPDATENET_PERIOD KLSRV_SP_FULLUPDATENET_PERIOD KLSRV_SP_INSTANCE_ID KLSRV_SP_MAX_EVENTS_IN_DB KLSRV_SP_OPEN_AKLWNGT_PORT KLSRV_SP_SCAN_AD KLSRV_SP_SERVERID KLSRV_SP_SERVERID_DPE KLSRV_SP_SERVER_AKLWNGT_PORTS_ARRAY KLSRV_SP_SERVER_PORTS_ARRAY KLSRV_SP_SERVER_SSL_PORTS_ARRAY KLSRV_SP_SERVER_SSL_PORTS_ARRAY_GUI KLSRV_SP_SYNC_LIFETIME KLSRV_SP_SYNC_LOCKTIME KLSRV_SP_SYNC_SEC_PACKET_SIZE KLSRV_SSL_CERT_RSA_BIT_NUMBER 

Секция 85, судя по всему, отвечает за события и ныне нам неинтересна. А вот в 87 есть что-то, на что стоит обратить внимание:

try

 try {     $Proxy.Connect($Params);     $KSCHost = New-Object -ComObject 'klakaut.KlAkHosts2';     $KSCHost.AdmServer = $Proxy;     $KSCSettings = $KSCHost.GetHostSettings($Proxy.GetProp('KLADMSRV_SERVER_HOSTNAME'), 'SS_SETTINGS');     $87 = $KSCSettings.Read('1093', '1.0.0.0', '87');     "KLSRV_SERVERINSTANCENAME = $($87.Item('KLSRV_SERVERINSTANCENAME'))";     "KLSRV_DATABASENAME = $($87.Item('KLSRV_DATABASENAME'))";     "KLSRV_CONNECTION_DATA =`r`n$($87.Item('KLSRV_CONNECTION_DATA') | % {"`t$_ = $($87.Item('KLSRV_CONNECTION_DATA').Item($_))`r`n";})";     Remove-Variable -Name '87';     Remove-Variable -Name 'KSCSettings';     Remove-Variable -Name 'KSCHost';     $Proxy.Disconnect(); } 

Результат

 KLSRV_SERVERINSTANCENAME = . KLSRV_DATABASENAME = KAV KLSRV_CONNECTION_DATA = 	KLDBCON_DB = KAV  	KLDBCON_DBTYPE = MSSQLSRV  	KLDBCON_HOST = . 

Тут я воспользовался одним из предыдущих кейсов, где упоминалось, что нужные данные следует брать именно из KLSRV_CONNECTION_DATA (тогда я ещё не знал, что это вообще такое, просто отложилось).

Ну, вот, в общем-то, и всё. Данные об используемой БД получены. Квест пройден.

Наверное, ещё неплохо бы набросать скрипт для прохождения по иерархии серверов. Здесь ничего загадочного не оказалось, всё было вполне по документации. Я написал скрипт, который выбирает UID родителя, UID самого сервера, экземпляр СУБД и имя БД и выводит их в stdout через разделитель.

Скрипт

 $SrvAddr = 'localhost:13291'  function EnumSrv(     $Pxy,     [bool]$IsAlive = $true,     [string]$ParentPxyId = 'Root' ) {     [string]$result = "$ParentPxyId";     if ($IsAlive) {         $result += "|$($Pxy.GetProp('KLADMSRV_SERVER_HOSTNAME'))";                  $Hosts = New-Object -ComObject 'klakaut.KlAkHosts2';         $Hosts.AdmServer = $Pxy;                  $Settings = $Hosts.GetHostSettings($Pxy.GetProp('KLADMSRV_SERVER_HOSTNAME'), 'SS_SETTINGS').Read('1093', '1.0.0.0', '87').Item('KLSRV_CONNECTION_DATA');         Remove-Variable -Name 'Hosts';              #'-------->   DB  Info <--------';         $result += "|$($Settings.Item('KLDBCON_HOST'))";         $result += "|$($Settings.Item('KLDBCON_DB'))";         #'-----------------------------';                  Remove-Variable -Name 'Settings';                  $SlaveSrvEnum = New-Object -ComObject 'klakaut.KlAkSlaveServers';         $SlaveSrvEnum.AdmServer = $Pxy;         $SlaveServers = $SlaveSrvEnum.GetServers(-1);                  $SlaveServers | % {             $Child = $_;             $TmpSrvId = $Child.Item('KLSRVH_SRV_ID');             $HostActive = $true;             try             {                 $TmpSrv = $SlaveSrvEnum.Connect($TmpSrvId, -1);             }             catch             {                 $HostActive = $false;             };             if ($HostActive) {$HostActive = ($TmpSrv.GetProp('IsAlive') -eq 1);};             $result += "`r`n$(EnumSrv -Pxy $TmpSrv -IsAlive $HostActive -ParentPxyId $Pxy.GetProp('KLADMSRV_SERVER_HOSTNAME'))";         };         Remove-Variable -Name 'SlaveServers';         Remove-Variable -Name 'SlaveSrvEnum';     };     return ("$result`r`n"); }  Clear-Host  $Params = New-Object -ComObject 'klakaut.KlAkParams' $Params.Add('Address', $SrvAddr) $Params.Add('UseSSL', $true)  $code =  @" using System; using System.Runtime.InteropServices;  public class PowershellComSecurity {    [DllImport("Ole32.dll", CharSet = CharSet.Auto)]    public static extern int CoSetProxyBlanket(IntPtr p0, uint p1, uint p2, uint p3, uint p4, uint p5, IntPtr p6, uint p7);     public static int EnableImpersonation(object objDCOM) { return CoSetProxyBlanket(Marshal.GetIDispatchForObject(objDCOM), 10, 0, 0, 0, 3, IntPtr.Zero, 0); } } "@ Add-Type -TypeDefinition $code  $Srv = New-Object -ComObject 'klakaut.KlAkProxy' [PowershellComSecurity]::EnableImpersonation($Srv) | Out-Null $Srv.Connect($Params)  "ParentPxyId|KLADMSRV_SERVER_HOSTNAME|KLDBCON_HOST|KLDBCON_DB`r`n" + (EnumSrv -Pxy $Srv); Remove-Variable -Name 'Srv'; Remove-Variable -Name 'Params'; 

Стенд маленький, поэтому результат оказался не очень впечатляющим:

Результат

 ParentPxyId|KLADMSRV_SERVER_HOSTNAME|KLDBCON_HOST|KLDBCON_DB Root|9d476a75-1e36-4c0e-8145-56e5b888df67|.|KAV 9d476a75-1e36-4c0e-8145-56e5b888df67|ef4fc3be-3abd-4322-ae35-2c50afdce780|.\KAV_CS_ADMIN_KIT|KAV 

Естественно, чтобы превратить точку в актуальное имя сервера, придётся поколдовать с KlAkHosts2.GetHostInfo(), но это уже не столь страшно, просто ещё сколько-то кода.

Техподдержка ЛК, естественно, пугала меня тем, что структура SS_SETTINGS в следующих релизах KSC может поменяться, поэтому так лучше не делать. К сожалению, даже мой внутренний перфекционист считает, что скрипт нельзя просто написать и забыть: при смене версии используемого ПО его по-любому придётся тестировать и отлаживать. Так что пока пользуемся тем, что есть, а проблемы будем решать по мере поступления, благо, техника уже отработана.

ссылка на оригинал статьи https://habrahabr.ru/post/278209/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *