At TL Tech we spend a lot of time tracking down hackers. What we find often illustrates important lessons in what not to do in web security. In this example, a user was a victim of a previous attack caused by a vulnerable PHP extension that has since been removed. The site owner evicted the attacker from the server by restoring from a known-good backup. And yet one month later the attacker returned. But how?
Following the Logs
We start by finding out whether any new files have been added to the site. We know that the attack started within the last 2 days, so we search for files newer than that. There’s a good chance that the attacker is using PHP files, so we’ll filter by that as well.
find . -name \*.php -mtime -2 ./admin/adm.php ./admin/scripts/wizard.php ./admin/scripts/adm.php ./admin/scripts/domain.php ./files/adm.php
We examine adm.php and see that it is, in fact, a backdoor script which allows the user to do pretty much anything he wants on the server. So now let’s see who’s been accessing it:
grep adm.php access_logs 76.10.222.80 - - [16:42:19] "GET /admin/adm.php HTTP/1.1" 404 76.10.222.80 - - [16:46:57] "GET /admin/adm.php HTTP/1.1" 404 76.10.222.80 - - [16:47:26] "GET /admin/adm.php HTTP/1.1" 200
Ah Ha! OK, so now we have the IP address of our culprit. The address belongs to privateinternetaccess.com, a company that offers VPN services to people who want to hide their real address. Let’s search the logs for all traffic coming from that IP.
grep 76.10.222.80 access_logs 76.10.222.80 - - [16:42:19] "GET /admin/adm.php HTTP/1.1" 404 76.10.222.80 - - [16:42:48] "GET /cpanel HTTP/1.1" 301 76.10.222.80 - - [16:46:57] "GET /admin/adm.php HTTP/1.1" 404 76.10.222.80 - - [16:47:26] "GET /admin/adm.php HTTP/1.1" 200
This log file tells a story: First the user attempted to access /admin/adm.php and got a 404 (file not found) error. Presumably he expected the file to still be there from his previous attack, but it had been removed. Next he went to /cpanel and got a 301 (redirect) return. This is to be expected; cPanel/WHM will redirect that URL to the control panel login. A few minutes later, he hit /admin/adm.php again and got another 404 error. Finally, half a minute later, he accessed /admin/adm.php one last time, and the file appeared! (see the “200″ status code). Presumably the cPanel bit can explain how that happened. So let’s check the cPanel log file for that IP address.
grep 76.10.222.80 cpanel/access_logs 76.10.222.80 - - [21:43:17] "POST /login/?login_only=1 HTTP/1.1" 301 76.10.222.80 - web1 [16:43:19] "GET /frontend/x3/index.html HTTP/1.1" 200 76.10.222.80 - web1 [16:46:29] "GET /json-api/cpanel?cpanel_jsonapi_module=Fileman... HTTP/1.1" 200 76.10.222.80 - web1 [16:46:29] "POST /json-api/cpanel HTTP/1.1" 200
Here’s another story if you know how to read it; the same IP address sends a login request in the first line, and in the second line he shows up logged in as web1. So he knows the password! The next steps are easy enough to guess; he sends a request to the file manager; the contents of which we can’t see, but we can pretty well guess that he uploaded the adm.php page that showed up in the other log file.
Finding the Password
Now that we know that he came in through the front door with a valid password, we’d like to figure out how he got it. We know that he had prior access to the server’s files, but that doesn’t give him the password. Unless, of course, the password is actually saved in one of those files. But would anyone save the cPanel password in their web files?
Well, we know from prior experience that one thing these attackers do is search all the files for the word “pass”. So let’s do that.
grep -IRl "pass" . ./wordpress/wp-config.php
Ah. A config file. We know that these files typically contain passwords, and we know that hackers make a habit of harvesting those passwords from these files. But this is a wordpress config file, and the only password it contains is the database password, not the cPanel password, right? Let’s look:
define('DB_NAME', 'web1_wordpress'); define('DB_USER', 'web1'); define('DB_PASSWORD', '482%0lkd31#3z');
And here’s the problem. The site owner created a database called web1_wordpress, but didn’t create a corresponding database user. Instead, he used the cPanel database user. Here’s what that means:
CPanel will automatically create a database user account with the same username and password as the primary cPanel login. This account has access to any database that the owner creates. That’s convenient, but in the interest of security, you’re always supposed to create a separate database account for your web application to use. Instead, this site owner just used his cPanel login name and password to configure WordPress.
This means that when the attacker first had access to the files, he was able find the cPanel login password buried in a configuration file. And though the site owner wiped out the account and restored the site from a backup, he did not change his password. Therefore, when the attacker realized he had lost access to the server, he simply logged in using the password he had previously obtained.
There are a few things we can learn from this story:
- Never save your control panel or FTP password in any configuration file
- Always create a separate database account when configuring a web app such as WordPress, Drupal, Joomla, etc.
- When recovering from an attack, always change all of your passwords. That can be tedious, but you never know what information might have leaked.