Catching Zencart Hack Attempts

Credit Card Theft

So, one morning I noticed quite a lot of 404 errors recording on a client’s web server logs and decided to take a look at them. Turns out that between 12:00am to 4:00am we would get about 4 requests a minute to different paths in the admin folder of our Zencart installation.

Keep in mind that we don’t have an admin folder, as any one from the Zencart community would tell you, ALWAYS RENAME THE ADMIN FOLDER AFTER INSTALLATION!.

So, what did the requests look like? They were calls to known old security issues that have been patched for a while now in the vanilla Zencart installation. They looked like this:

  • /admin/sqlpatch.php/password_forgotten.php
  • /admin/record_company.php <-- this was fun(scarry?) to see. They were attempting code injection

So what can we do with the knowledge that we are getting hack attempts on the old(non existant) admin folder?

Simple, record their IP addresses. Then block them at the firewall level(if you like). Here is a simple setup of a php file that will record the requests attempts in the admin folder, then you can review those records and figure out if you want to ban any IPs or not. We’ll use a .htaccess file with mod_rewrite module to redirect all requests in the admin folder to a single php script.

This is the script that will record all the request attempts: /admin/hackthis.php

<?php
//error_reporting(E_ALL & ~E_NOTICE);

$toEmail = false;
$fromEmail = 'noreply@mydomain.com';
$sendEmailAlert = false;

function getClientIP() {
	// taken from: http://www.kavoir.com/2010/03/php-how-to-detect-get-the-real-client-ip-address-of-website-visitors.html
	$test = array(
                'HTTP_CLIENT_IP',
                'HTTP_X_FORWARDED_FOR',
                'HTTP_X_FORWARDED',
                'HTTP_X_CLUSTER_CLIENT_IP',
                'HTTP_FORWARDED_FOR',
                'HTTP_FORWARDED',
                'REMOTE_ADDR');

	foreach ($test as $key) {
            if (array_key_exists($key, $_SERVER)) {
                foreach (explode(',', $_SERVER[$key]) as $ip) {
                    if (filter_var($ip, FILTER_VALIDATE_IP) !== false) {
                        return $ip;
                    }
                }
            }
        }

	return "";
}

// move dir to root, so we can include zencart include files
$root = realpath(dirname(__FILE__) . "/../") . '/';
chdir($root);
require($root . 'includes/application_top.php');

// get the attempted url
$url = $_SERVER['HTTP_HOST'] . '/admin/' . $_GET['__url'];

// the ip of the request
$ip = getClientIP();

// lets record the request variables into a json string so we can fit it in a text column
$request_data = json_encode(
    array(
        'REQUEST' => $_REQUEST,
        'GET' => $_GET,
        'POST' => $_POST,
        'RAW_POST' => file_get_contents('php://input')
    )
);

// reverse dns look up of the ip
$host = ($ip?gethostbyaddr($ip):'');

// the request referer, can't really trust it... but we'll record it anyways
$referer = $_SERVER['HTTP_REFERER'];

// quietly record this attempt at retrieving this file. It's probably a hack attempt
$query = "
    INSERT INTO hack_attempt
        (url, ip, referer, request, host)
    VALUES
        (:url, :ip, :referer, :request, :host)";

$query = $db->bindVars($query, ':url', $url, 'string');
$query = $db->bindVars($query, ':ip', $ip, 'string');
$query = $db->bindVars($query, ':referer', $referer, 'string');
$query = $db->bindVars($query, ':request', $request_data, 'string');
$query = $db->bindVars($query, ':host', $host, 'string');

$db->Execute($query);

if ( $toEmail ) {
    $emailBody = "[$host]$ip Attempted to access admin files!";
    $header = 'FROM: ' . $fromEmail . "\r\n" .
		  'X-Mailer: PHP/' . phpversion();

    @mail($toEmail, "[HACK ATTEMPT] $url", $emailBody, $headers);
}

// send out a 404
header("HTTP/1.0 404 Not Found");

// you can modify the rest as you like from here
?>
<!DOCTYPE html>
<html>
	<head>
		<title>File Not found!</title>
	</head>
	<body>
		<p>File Not Found!</p>
	</body>
</html>

You can optionally set $toEmail to your email address to get an alert on every request attempt, but that may get out of hand if you are getting bombarded by requests

The sql table looks like this:


CREATE TABLE  `hack_attempt` (
  `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
  `url` varchar(512) NOT NULL,
  `ip` varchar(64) default NULL,
  `referer` varchar(512) default NULL,
  `request` text,
  `host` varchar(64) default NULL,
  PRIMARY KEY  (`timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

and the /admin/.htaccess file looks like this:


RewriteEngine On
RewriteRule (.*) hackthis.php?__url=$1 [QSA]

So, from now on you’ll get a record of all IPs trying to access anything in the admin folder. But do remember that this will only work if you had renamed your admin folder to something else. So, the idea here is to catch bots trying to access admin files that don’t exists and block them from ever visiting the site again. You do need to add:


Disallow: /admin/

in your robots.txt file to keep google bot, or other search engine bots from going in that directory. So you should only get misbehaving or malicious bots recorded in your hack_attempt table.

So, where to go from here? Well, you can take those IP addresses and add them to your firewall filters so they can never attempt anything on your server ever again. Some people will argue that IP blocking is harsh and easy to circumvent by using proxies, but what can you really do when seeing blatant hacking attempts from those IPs?

Quick and dirty Zencart and Drupal theme integration

So, recently I had to find a way to combine a zencart shopping cart and a drupal blog site into one website. There are no off the shelf ways of doing this that I could find so I wrote my own.

What I did to make my life easier was have drupal “fit” itself around the zencart template. That way any changes that need to be done to the site template, can be done to zencart only, and drupal would just mirror the changes.

The way to achieve this is to have zencart serve an empty page(not blank!) that my drupal installation could scrape on demand and use as a template. Then on the zencart template, I strategically placed html comments marking beginnings and endings of blocks that drupal could parse out and place it’s content around. Below we have an example of such a setup. Imagine this snippet comes from the empty zencart page:

< html>
<!– bof-head_tag –>
<head>
<!– eof-head_tag –>
<!– bof-title –>

<title>Zencart page title</title>

<!– eof-title –> 
<!– bof-zc_meta–> 
<!– eof-zc_meta –!> 
</head>
<!– eof-html_top –>
<!– bof-body_top –>

<body>
<!– eof-body_top –>
<!– bof-body_bottom–>

</body>
<!– eof-body_bottom–>
</html>

 

The example is very simple and not what a site template should look like, there would be other tags there placing columns and such, but for the purpose of this example it will do. The html comments in bold above can be replaced in drupal with any of the drupal content needed to make the template work there. For example, the <!– bof-title –> <!–eof-title –> pair are replaced with the drupal title content instead of keeping the zencart title. This way we can pretty much reconstruct any zencart template in drupal by either replacing the comment pairs or selectively rendering the content of the pairs as we need them on the drupal template. Do note that to make life easier I use the Zen Drupal theme. It makes this whole process much, much cleaner and nearly trivial( if you know what you are doing ).

As an example, here is the site of the client that needed the zencart/drupal template integration: http://www.monkeyhydro.com, If you click on the blog link, drupal loads and scrapes the empty page and places its blocks where required.

I did have to add a few other things to the setup, like account log in and shopping cart item count. This works by fetching the info using ajax from zencart. That’s just a little json web-service that gives drupal enough info to reconstruct the links on the top right corner. Since there was no need(at the moment) for drupal accounts to be synced or integrated to drupal, there is no account login for drupal. That may come in the future still…

A full example may take a little time to get ready, but will soon come. I will package the zencart webserivice and drupal parsing code for the zencart template. Once that’s done I’ll be posting it here.

Also, as a side note. This process can be also done in reverse, where the site is drupal centric so the main template is maintend in drupal and zencart does the scraping and fitting. I’ve also done this once but does prove to be a bit of longer process since zencart templates are spread through so many template files.