Back to Home

Kaspersky Security Center - the struggle for automation

kaspersky security center · powershell · klakaut

Kaspersky Security Center - the struggle for automation

Oddly enough, I found on Habré only one article on this topic - the one in the sandbox and the very unfinished one that actually contains a small piece of a little remade help on the product. And Google is silent at the request of klakaut.

I am not going to tell how to administer the Kaspersky Security Center hierarchy (hereinafter referred to as KSC) from the command line - I have never needed this before. I just want to share some ideas about automation tools with those who may need it, and I will analyze one case that I had to deal with. If you,% habrauser%, this topic will be interesting - welcome under cat.

Historically, as a means of anti-virus protection at work, I prefer Kaspersky Lab products (hereinafter referred to as LC). Perhaps, we will leave behind the scenes the reasons and other holy wars of personal opinions.

Naturally, I would like to centrally deploy, protect, protect and prevent the drawing of beautiful graphs, integrate into existing monitoring systems and do other things from work with my patient to a healthy server. And if everything is more or less in order with deployment and protection (LK even has some kind of online courses on products ), then integration is already much sadder: in the latest version of KSC 10.2.434 , integration has already appeared with two SIEM: Arcsight and Qradar. That's all.

KSC provides as many as 2 interfaces for integration into something of its own:

  • klakdb : in the KSC database there are a number of views with names starting with “v_akpub_”, from which you can get some information about the state of anti-virus protection.
  • klakaut : DCOM object that allows you to script work with KSC.

On both points there is documentation in the KSC, as is indicated in the articles to which I gave links. True, this documentation raises a number of questions that you can ask the CompanyAccount corporate product support service and get an answer like “Refined information. Unfortunately, scripts support for klakaut not affected. " .

The disadvantages of klakdb are obvious: in order to directly access the database, you need to have access to this database, which leads to the need for extra gestures to create access rules in firewalls, configure access rights on the DBMS servers and other extremely unpleasant pastimes. Well, plus monitoring the relevance of all these rules, of course. It becomes especially interesting when there are 20+ servers - and all in different branches, each of which has its own administrators.

Well, the cherries on this cake: access exclusively Read Only and, to put it mildly, incomplete information about the environment. The advantages are not so obvious, but there are also: you can very quickly upload statistical information on the number of hosts, the versions of antiviruses and anti-virus databases used on one server, and most importantly, you can work very conveniently (convenience depends on SQL knowledge) with the anti-virus infrastructure events registered on KSC .

klakaut in this regard is much more interesting: by connecting to the root server of the hierarchy, you can use KSC to go through this hierarchy and gain access to all the necessary data. For example, build a tree of KSC servers with a note, which of them is alive and which is not, run tasks, move computers, and generally give free rein to imagination.

Cons too, of course: long and difficult. If you need to collect some statistics, you will first need to write a script for a long time, and then catch bugs for a long time, wait for it to work.

Naturally, no one forbids (at least I don’t know about this) to use both mechanisms together: for example, go through the server hierarchy using klakaut, get a complete list of KSC servers with information about the databases used, and then transfer this information to more others automation tools that remove obsolete rules from firewalls, create new ones, give access permissions and bring coffee to bedthey will edit the list of data sources in your monitoring system, which, in turn, will poll the list and, if it detects any deviations, will do something good with klakaut. Well, or just register the incident in the tracker. Then administrators will do something good manually.

Inspired by all these considerations, I wrote my first script:

There he is
$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';


And launched it on the server:

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

The beginning is good.

Mistress note
If you use js, this error does not occur. I wonder why.

Having opened the KSC console, I was convinced that with the rights I have everything in order.

Unfortunately, KSC does not log failed login attempts. Correspondence with the vendor showed that logging of unsuccessful login attempts can be turned on (it was a separate fascinating story, which, by the way, had not yet ended), but this particular attempt refused to get into the logs anyway.

It would seem that you can do this:

Invalid script
$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';


In this case, there will be no errors, but it did not seem to me a wonderful idea to indicate the login with the clear text password in the script. A new correspondence with the technical support of LK gave me the following recommendation:

It is necessary to set in the COM settings, on the tab Default Properties:
Default Authentication Level: Packet
Default Impersonation Level: Delegate

This instruction made the original script work, but it seemed dubious to me in terms of security, so I decided to dig a little deeper. After some time of searching, I found a kind person who told me how to set the specified authentication and impersonation levels for a particular object, and not to allow everyone to do it all at once:

Correct script
$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';


So the script did not give out any errors. First quest completed.

Mistress note
In general, in a combat environment, it would be nice to check the return value for errors when calling EnableImpersonation, and not redirect it to nowhere, as I did.

The next task: to obtain data from the KSC about the database used.

But here everything is complicated: the documentation on how to do this is silent. The study of the KlAkProxy class did not reveal anything interesting, except for the KLADMSRV_SERVER_HOSTNAME parameter, which turned out to be the identifier of the computer on which KSC is installed.

Let's move on to the computer, for this there is a special class KlAkHosts2. To reduce the amount of code, I will cite only the contents of the try block:

try block
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();
}


Please note: the $ Params variable that I used when connecting to KSC is an instance of the KlAkParams class. And the variable $ HostParams with, in my opinion, similar functionality, is an instance of the KlAkCollection class. Why are different classes used? I'm afraid to even imagine. Apparently, the fact that SetAt takes only integer values ​​as the first argument is a very fundamental point.

This code returned the value "KSC", which means I'm on the right track.

The GetHostInfo method of the KlAkHosts2 class is well documented, but does not contain the information I need. Alas and ah. But there is a GetHostSettings method. The entire description for which boils down to the following:

Returns host's settings as setting storage.

Let's take a look inside:

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


Result
------------------------
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


In klakaut.chm there is a section “List of KLHST_WKS_PRODUCT_NAME and KLHST_WKS_PRODUCT_VERSION values ​​for products”, where you can see that the PRODUCT field for KSC should be 1093, respectively, everything else can be safely ignored. So far, at least.

Having run my eyes over the names of the sections, I decided to look at 85 and 87, since the others were not very similar to what I needed.

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


Result
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


Section 85, apparently, is responsible for the events and is now uninteresting to us. But in 87 there is something worth paying attention to:

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


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


Then I took advantage of one of the previous cases where it was mentioned that the necessary data should be taken from KLSRV_CONNECTION_DATA (then I did not know what it was at all, it was just postponed).

Well, that’s basically it. Data on the used database is received. Quest completed.

It would probably be nice to sketch a script to walk through the server hierarchy. There was nothing mysterious here, everything was completely according to the documentation. I wrote a script that selects the UID of the parent, the UID of the server itself, the DBMS instance and the database name and displays them in stdout via a separator.

Script
$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';


The stand is small, so the result was not very impressive:

Result
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


Naturally, to turn the point into the actual server name, you have to conjure with KlAkHosts2.GetHostInfo (), but this is not so scary, just some more code.

LK tech support naturally scared me that the SS_SETTINGS structure could change in future KSC releases, so it's best not to do that. Unfortunately, even my internal perfectionist believes that the script cannot be simply written and forgotten: when changing the version of the software used, it will have to be tested and debugged in any way. So for now, we use what we have, and we will solve problems as they become available, fortunately, the equipment has already been worked out.

Read Next