New Security Plans

Due to repeated problems with hackers on servers at my day job, I’ve developed a new security plan that I’m going to share in a second. First, a little background, and then I’ll list my requirements that I used to develop the plan.

First, we have no official system administrator, and we have 15 or more servers right now, all running CentOS 5. No one really has time to do everything that should be done to keep them up to date and secured. I’ve made loud noises about this for some time, but for various reasons, nothing much has been done about this.

So, my requirements for the new security setup are: 1. Extremely low maintenance once it is in place. I understand that updates are important, but we don’t have someone to do that full time. 2. Because of that lack of personel, it should reduce exposure of possibly vulnerable services as much as possible. 3. Simple to work with and update as needed.

I did some research, and settled upon the following security ‘layers’ if you will.

First layer: Cracklib configured to require much more secure passwords. Most if not all of our compromised servers have been caused by weak passwords on key accounts.

Second layer: SSH daemon configured to not allow remote root login. There is no reason this should be necessary.

Third layer: IP Tables to lock down system so that only ports that are required for the purpose of the server are available publiclly, and all other ports are only open to the systems that need them. This includes locking the SSH and cPanel ports to only be available by default from the main office network, and any static home IPs of workers.

Fourth layer: Port Knocking daemon set up to give the ability to open the SSH port as needed from other locations. I’ve set it up with a 10 port sequence randomly generated from the 10,000 – 49,151 range, and randomly selected as TCP or UDP. Even at only 10 ports long, there’s 8.66 × 1048 possible sequences, which is plenty secure, when in combination with all the other layers. When the knock sequence is detected, it opens port 22 to the origin IP for 15 seconds, and then closes it again.

Fifth layer: DenyHosts daemon set up using global pool of blocked hosts to keep anyone who somehow happens to get through the port knocking from being able to bruteforce the SSH passwords.

I’ve already implemented the five layers above on several servers, and tested them for a while. It seems to be working flawlessly so far, and it should make the servers much more secure. As part of the implimentation process, I have mandated that we re-install the OS on the servers, to make sure they have a clean system with no backdoors built in. I don’t think there’s much chance of someone having a back door in place that could compromise this, but I’m not taking the chance.

If you have any further suggestions, enhancements, or questions, please let me know. I’m always willing to look at improving this plan further.

CakePHP Shells, and Cron Jobs

As part of the same project that resulted in my last posting, I needed to write several processes that would be managed by cron. In the past, at the Yahoo contract, we had come up with a way to do this involving a customized version of the basic CakePHP index.php file. However, his required us to place the cron code on the main controllers, and extra protections to ensure that the code was inaccessible from the main web interface. I was never thrilled with the solution, but it was the best we had at the time.

So for this project, I looked into the concept of ‘Shells’, which are basically command-line interfaces to a CakePHP application. It quickly became obvious that this was the best way to go about handling the cron jobs for a CakePHP application. It allows you to place all your sensitive cron code completely out of reach of the web facing part of the application, increasing security and decreasing complexity. It’s really quite simple as well.

The process is basically outlined in this section of the CakePHP manual, and should be simple enough to follow. The end result is code like this:

class BillingShell extends Shell {
	var $uses = array('OrderLineItem', 'Division');
 
	function main() {
		echo "Please specify which billing task to run. Currently, the only choice is 'daily'.\n\n";
	}
 
	function daily() {
		date_default_timezone_set('America/Denver');
		App::Import('Component','Cbg');
		$this->Cbg = new CbgComponent();
 
		$this->create_invoices();
		$this->process_invoices();
	}
 
	// Other functions not shown
}

And you can run it as a cron like this:

# m h  dom mon dow   command
*/5 * * * * /full/path/to/cakeshell billing daily -app /full/path/to/app

Automatic DB Field Encryption in CakePHP

UPDATE: I’ve posted an improved version of this behavior here

I’ve written the following behavior for a project I recently completed in Cake, and I thought it would be worth sharing:

class CryptableBehavior extends ModelBehavior {
	var $settings = array();
 
	function setup(&$model, $settings) {
		if (!isset($this->settings[$model->alias])) {
			$this->settings[$model->alias] = array(
				'fields' => array()
			);
		}
 
		$this->settings[$model->alias] = array_merge($this->settings[$model->alias], $settings);
	}
 
	function beforeFind(&$model, $queryData) {
		foreach ($this->settings[$model->alias]['fields'] AS $field) {
			if (isset($queryData['conditions'][$model->alias.'.'.$field])) {
				$queryData['conditions'][$model->alias.'.'.$field] = $this->encrypt($queryData['conditions'][$model->alias.'.'.$field]);
			}
		}
		return $queryData;
	}
 
	function afterFind(&$model, $results, $primary) {
		foreach ($this->settings[$model->alias]['fields'] AS $field) {
			if ($primary) {
				foreach ($results AS $key => $value) {
					if (isset($value[$model->alias][$field])) {
						$results[$key][$model->alias][$field] = $this->decrypt($value[$model->alias][$field]);
					}
				}
			} else {
				if (isset($results[$field])) {
					$results[$field] = $this->decrypt($results[$field]);
				}
			}
		}
 
		return $results;
	}
 
	function beforeSave(&$model) {
		foreach ($this->settings[$model->alias]['fields'] AS $field) {
			if (isset($model->data[$model->alias][$field])) {
				$model->data[$model->alias]['cleartext_'.$field] = $model->data[$model->alias][$field];
				$model->data[$model->alias][$field] = $this->encrypt($model->data[$model->alias][$field]);
			}
		}
		return true;
	}
 
	public function encrypt($data) {
		if ($data !== '') {
			return base64_encode(mcrypt_encrypt(Configure::read('Cryptable.cipher'), Configure::read('Cryptable.key'), $data, 'cbc', Configure::read('Cryptable.iv')));
		} else {
			return '';
		}
	}
 
	public function decrypt($data, $data2 = null) {
		if (is_object($data)) {
			unset($data);
			$data = $data2;
		}
 
		if ($data != '') {
			return trim(mcrypt_decrypt(Configure::read('Cryptable.cipher'), Configure::read('Cryptable.key'), base64_decode($data), 'cbc', Configure::read('Cryptable.iv')));
		} else {
			return '';
		}
	}
}

All you need to do is add three lines to your bootstrap, and then load the behavior in any model you want to use it.

Here are the lines for your bootstrap:

Configure::write('Cryptable.cipher', 'rijndael-192');
Configure::write('Cryptable.key','random key string here');
Configure::write('Cryptable.iv', base64_decode('base64 encoded IV here')); // Create with mcrypt_create_iv with the appropriate size for your cipher

Here’s an example of how to load it in your model:

	var $actsAs = array(
		'Cryptable' => array(
			'fields' => array(
				'password'
			)
		)
	);

If you need to encrypt or decrypt a field outside of the normal find methods, you can simply call those methods on the model, passing in the string that needs worked on.

Download Cyptable Behavior

Review – The Hero of Ages

I recieved this book as a contest reward from Library Thing, and I had very high hopes. I had already read the first two books, and was looking forward to the third book eagerly. Unfortunately, it took two months to arrive, during which I was on pins and needles.

So a few weeks ago, it finally arrived, and I’ve been reading it. I’m happy to report that my expectations were met and exceeded in every regard. I felt that book two was somewhat weaker than the first book, though still exceptional. But this book was a big move back in the right direction. Brandon’s writing managed to keep me on the edge of my seat through the whole book, despite the multiple plot threads, and the final conclusion was a very good surprise that made perfect sense, even looking back over the whole trilogy.

The pacing was excellent, and the writing impeccable, as I’ve come to expect from Brandon’s work. Any fans of his other series, or of epic fantasy in general should definately pick up this book, along with the first two in the series. In fact, I strongly reccomend any of his books for anyone who reads fantasy at all.

30 years old? Really?!

I’m 30 today. It’s kind of surreal, but probably not for the reasons most people think it is. See, I don’t /feel/ 30. I barely feel like I’m not a teenager anymore, and yet… I’ve been married for nearly ten years, and I’m 30. Crazy.

My wife got me a new binary clock from thinkgeek for by birthday. I’d had one a long time ago, but I lost the power adapter for it, and then lost the clock. So now I have a new one. They are awesome. :) Also, she got me a ‘Dinosaur Plant’ (Selaginella Lepidophylla) for my desk. It’s in the process of opening up now, which is pretty freaky. They come as this dried up lump of brown leaves with some roots sticking out of the bottom, and in the space of a few hours, open up into a green leafy thing. Supposedly, they can live for 50 years or more without water.

Anyway… been a good birthday so far, other than having to work. But really, even that doesn’t bother me. Heck, last year, I forgot it was my birthday until my wife reminded me.

Have a good day, everyone!