Security::cipher() problems and Suhosin

This problem can now be solved by setting the encryption type in the CookieComponent to rijndael.

Over a week ago I migrated one of my sites to a MediaTemple (ve) Server. I installed nginx, PHP 5.3.2 (with Suhosin), MySQL, APC and Memcache. After a little configuration and debugging, everything was working smoothly except encrypted cookies. I was using my AutoLogin component and login never persisted, but regular cookies worked correctly. I thought it would be an easy fix but I was wrong (I will post another blog on this topic).

While trying to figure out the encryption problem, I debugged the CookieComponent. I tested each function individually around a fake array. I tested __implode(), __encrypt(), __explode() and Security::cipher(). My results were all over the place. When I tested the script locally, everything worked correctly and the ciphering process worked as it should (below). But on the live server, all the encryption strings would change each refresh and the un-cipher would never work.

Implode:
username|gearvOsh,password|dsadnas79dn7n1279dnadnadb1p2sdasd,hash|761c1168c645784b78f5f967160ab390,time|1286152916
Ciphered:
z~¡|ßÛí�"� =�'¥¾E¨ÿÏy� íåÇv¸”^ëS ¦ì•ˆxuŽ?��ãbm}ÜfÇ“„bËV´w±(ÐU�"éQ’Qñn²^6¿2r‰½ùé‹ÄáÄ,‡ ›d¤<ó`'ëÆìÏg$V")ËdÁ&µ‰
Base64 encode:
Q2FrZQ==.en6hfN/b7dMfij3Spb4ERaj/z3ng7eXHdriUXutTIKbslYh4dR6OP8bjYm193GbHk4Riy1a0d7Eowx+QVZbpUZJR8W6yXja/MnKJvfnpixsIxOHELIcgm2QHpDzzYCfrxhrsz2ckVggijwcpy2TBJrWJ
Using Cookie::__encrypt():
Q2FrZQ==.en6hfN/b7dMfij3Spb4ERaj/z3ng7eXHdriUXutTIKbslYh4dR6OP8bjYm193GbHk4Riy1a0d7Eowx+QVZbpUZJR8W6yXja/MnKJvfnpixsIxOHELIcgm2QHpDzzYCfrxhrsz2ckVggijwcpy2TBJrWJ
Base64 decode:
z~¡|ßÛí�"� =�'¥¾E¨ÿÏy� íåÇv¸”^ëS ¦ì•ˆxuŽ?Æãbm}ÜfÇ“„bËV´w±(ÐU�"éQ’Qñn²^6¿2r‰½ùé‹ÄáÄ,‡ ›d¤<ó`'ëÆìÏg$V")ËdÁ&µ‰
De-ciphered:
username|gearvOsh,password|dsadnas79dn7n1279dnadnadb1p2sdasd,hash|761c1168c645784b78f5f967160ab390,time|1286152916

As you can see from the debug (on my local machine), the beginning and resulting strings are the same. I then found out the problem was with Security::cipher() and srand(). I later found out that Suhosin was overriding the srand() and mt_srand() functions, which was causing my seed to always change. I tried disabling Suhosins overwrites, but to no avail.

// Did not work
ini_set('suhosin.mt_srand.ignore', 0);
ini_set('suhosin.srand.ignore', 0);

After some googling and a lot of frustration, I finally found a solution. Thanks to Edgar Valarezo, I was able to get my encrypted cookies working properly. However, I had to overwrite CakePHP's core Security class and will need to maintain it every time I upgrade Cake. Here is the new cipher() snippet for your convenience.

function cipher($text, $key = '') {
    $key .= Configure::read('Security.cipherSeed');
    $out = '';
    $textLength = strlen($text);
    $keyLength = strlen($key);
    $k = 0;
    for ($i = 0; $i < $textLength; $i++) {
        $seed = md5($key . $key[($k++) % $keyLength]);
        $mask = hexdec($seed[6] . $seed[9]); // :)
        $out .= chr(ord($text[$i]) ^ $mask);
    }
    return $out;
}

I see this as a temporary fix and would love any information on this topic. If you ran into this same problem, how did you correct? Is it possible to seed correctly with Suhosin? Thanks again!

AJAX calls in CakePHP: The JSON Response

The controller and HTTP response are now working correctly, so it is time to display the response message to the client. We will achieve this by utilizing jQuery to output our JSON. The plan is to show a success message if the JSON passed or display a list of errors if it has failed. To display the messages we need to create our HTML elements that will hold the responses.

<div id="responseSuccess" class="responseBox" style="display: none"></div>
<ul id="responseError" class="responseBox" style="display: none"></ul>

Before we can write our Javascript to handle the JSON, we need to first inspect the JSON response. Our JSON "data" message can come in 3 possible formats: a simple string, an array containing the error messages, or an object containing the error messages / post data with their key values. Below are a couple examples of how our JSON response could be formatted:

// Single string error
{"success":false,"data":"No username/password","code":-1}
// An array containing the errors
{"success":false,"data":["No username","No password"],"code":-1}
// Errors / Post data and their key
{"success":false,"data":{"username":"No username","password":"No password"},"code":-1}
{"success":true,"data":{"username":"Miles","password":"p4ssw0rd"}}

Now this poses a significant problem. If our response data can be sent as 3 possible formats, we need to write our Javascript to be able to handle all possible variations. We can do this by creating a loop for the arrays/objects and store the values in a custom array, and for the single string just return the message. The following code block can do just that, and all we need to do is place it within the "success" parameter of our AJAX call.

function login() {
	var data = $("#UserAddForm").serialize();
	$.ajax({
		type: "post",
		url: "/ajax/login/",
		data: data,
		dataType: "json",
		success: function(response, status) {
			// Response was a success
			if (response.success) {
				$("#responseSuccess").html(response.data).slideDown();
			// Response contains errors
			} else {
				var errors = [];
				if (typeof(response.data) == ("object" || "array")) {
					$.each(response.data, function(key, value) {
						var text = isNaN(key) ? key + ": " + value : value;
						errors.push("<li>"+ text +"</li>");
					});
				} else {
					errors.push("<li>"+ response.data +"</li>");
				}
				errors = errors.join("\n");
				$("#responseError").html(errors).slideDown();
			}
		}
	});
	return false;
}

Pretty simple right? We can improve on this code by adding a setTimeout() that will remove the response box after 5 seconds. Additionally we can add the AJAX error argument that is called if the AJAX mechanism fails as a whole.

// Remove box after 5 seconds
setTimeout(function() {
	$(".responseBox").slideUp();
}, 5000);
error: function(XMLHttpRequest, textStatus, errorThrown) {
	$("#responseError").html("<li>An unexpected error has occurred.</li>").slideDown();
}

Our Javascript is now complete, but we could improve on it yet again. Say you have multiple AJAX calls that all utilize the same type of callback mechanism. It would be a major pain and waste of time to write all that code over and over again. What we can do is separate the code into 3 separate functions, all handling certain aspects of the logic. Our final code may look something like the following.

/**
 * Fire an AJAX call to login the user
 */
function login() {
	var data = $("#UserAddForm").serialize();
	$.ajax({
		type: "post",
		url: "/ajax/login/",
		data: data,
		dataType: "json",
		success: function(response, status) {
			handleCallback(response, status);
		},
		error: function(XMLHttpRequest, textStatus, errorThrown) {
			handleError(XMLHttpRequest, textStatus, errorThrown);
		}
	});
	return false;
}
/**
 * Handle the AJAX callbacks
 */
function handleCallback(response, status) {
	// Response was a success
	if (response.success) {
		$("#responseSuccess").html(response.data).slideDown();
	// Response contains errors
	} else {
		var errors = [];
		if (typeof(response.data) == ("object" || "array")) {
			$.each(response.data, function(key, value) {
				var text = isNaN(key) ? key + ": " + value : value;
				errors.push("<li>"+ text +"</li>");
			});
		} else {
			errors.push("<li>"+ response.data +"</li>");
		}
		errors = errors.join("\n");
		$("#responseError").html(errors).slideDown();
	}
	// Remove box after 5 seconds
	setTimeout(function() {
		$(".responseBox").slideUp();
	}, 5000);
	return false;
}
/**
 * Handle an AJAX failure
 */
function handleError(XMLHttpRequest, textStatus, errorThrown) {
	$("#responseError").html("<li>An unexpected error has occurred.</li>").slideDown();
}

Now there are multiple ways for your to handle a JSON response, but the approach above is a pretty straight forward implementation of displaying a success or failure message. Be sure that when you write your AJAX requests, that the $.ajax() function has a dataType of json, or your JSON response will completely fail.

AJAX calls in CakePHP: The Controller

Now that you have setup the Javascript and view to properly fire off an AJAX call, it's time to process the call within the Controller. The AJAX call was firing off to AjaxController::login(), so lets begin by creating the action. What we need to do is check to see if the username and password is set (validation), then attempt to login the user by using the AuthComponent. If the login is successful, or even if it fails, we will return a JSON response.

Below would be a basic setup to accomplish what we need to do. However, in your actual application you would apply more strict validation and sanitization, but for the sake of the tutorial I will not.

function login() {
	$this->layout = 'ajax'; // Or $this->RequestHandler->ajaxLayout, Only use for HTML
	$this->autoLayout = false;
	$this->autoRender = false;
	$response = array('success' => false);
	if (!empty($this->data['User']['username']) && !empty($this->data['User']['password'])) {
		if ($this->Auth->login($this->data)) {
			$response['success'] = true;
			$response['data'] = $this->data['User'];
		} else {
			$response['data'] = 'Username/password combo incorrect';
			$response['code'] = 0;
		}
	} else {
		$response['data'] = 'No username/password';
		$response['code'] = -1;
	}
	$this->header('Content-Type: application/json');
	echo json_encode($response);
	return;
}

As an added bonus, I will supply an example of how to achieve the same result above using my AjaxHandlerComponent. The component does all the necessary preparations and output leaving you to write less code.

function login() {
	if ($this->AjaxHandler->valid($this->data['username'], true) && $this->AjaxHandler->valid($this->data['password'], true)) {
		if ($this->Auth->login($this->data)) {
			$this->AjaxHandler->response(true, $this->data);
		} else {
			$this->AjaxHandler->response(false, 'Username/password combo incorrect', 0);
		}
	} else {
		$this->AjaxHandler->response(false, 'No username/password', -1);
	}
	$this->AjaxHandler->respond();
	return;
}

Basically what we need to do, to get a proper JSON response is to build the response array. We create an array with a success variable that would be a true or false. If the success is true that means the core logic that we want completed was a success, else the AJAX logic failed and the user should try again. We also have a data index that would house the error/success message(s), an array of data to use, or anything else you might need. Lastly we have a code index which could be used to differentiate the levels of errors and their importance. Once we have our response array complete, we set the correct headers and respond it back while encoding it with json_encode().

On top of building the response we need to configure the controller a bit. We need to disable the view and layout from rendering or we will receive the "Missing View" errors within Cake, and we also need to remove the layout so that our response is not surrounded by HTML (unless you want to respond with HTML, but we are using JSON). We do this by setting autoLayout and autoRender to false -- furthermore we set the layout to ajax which would be required for HTML responses.

You must be thinking to yourself, "Wow that was easy, we are now done!", but you are wrong! We still have one final step, validating that the request is a legitimate AJAX call. To achieve this we will use the RequestHandler::isAjax() method. Since all of our AJAX calls are housed in the AjaxController, we can place this code in the beforeFilter() so it applies to all actions. If your AJAX actions are within your primary controllers, you will need to add a bit more logic.

I will be using the SecurityComponent in my examples, but you can use die() or something similar in place of blackHole(), like Controller::redirect().

// Must be disabled or AJAX calls fail
$this->Security->validatePost = false;
if (!$this->RequestHandler->isAjax()) {
	$this->Security->blackHole($this, 'You are not authorized to process this request!');
}
// Use the following code in place of blackHole()
// $this->redirect(null, 401, true);

The previous code should blackhole any request that does not contain the XMLHttpRequest header. To further build on this code, we can check to make sure the AJAX request is coming from the current domain, if not then kill it. Our final beforeFilter() should look something like the following.

function beforeFilter() {
	parent::beforeFilter();
	// Must be disabled or AJAX calls fail
	$this->Security->validatePost = false;
	if (!$this->RequestHandler->isAjax()) {
		$this->Security->blackHole($this, 'You are not authorized to process this request!');
	} else {
		if (strpos(env('HTTP_REFERER'), trim(env('HTTP_HOST'), '/')) === false) {
			$this->Security->blackHole($this, 'Invalid referrer detected for this request!');
		}
	}
}

The controller code should now be complete and ready for your amazing AJAX use. We could however improve it a little more by utilizing the RequestHandlerComponent and some of its methods, but ill leave that up to you to adventure into. In the next chapter we will go over the response and how to interact with the JSON.

Protecting your forms with the Security Component

Many users are unaware of this feature as it is not stated within the Cookbook, but the SecurityComponent by default will secure and protect your forms (if you have added Security to the $components array). What does that mean you ask, well it's simple. The Security will add hidden token fields within all your forms that will protect you against many types of robots. An example of a token field can be seen below.

Array
(
    [_Token] => Array
        (
            [key] => 40bbf3ac6cb4cd9bfaa617c088aa938bb398e80f
            [fields] => b5a93a2492bd2e6016856828d8046ba1f6f6200b%3An%3A0%3A%7B%7D
        )
)

These token fields are a dynamically generated hash, based on all the fields currently available in a specific form. On top of this, the Token will only last for a limited duration, so if your session takes forever on a certain form, you will be blackholed. Now on to the term blackhole, this basically means your form will post to a blank/white page and will fail.

Using Javascript to change input values

If you use Javascript to change a hidden inputs value, the Security will blackhole the form because it checks to see if any hidden fields have changed values (if it has, its usually a bot). To bypass this check, you would add this code to your controllers beforeFilter(). The array should consist of the field names you DO NOT want the Security to validate, so in this case it would be the name of our hidden field.

$this->Security->disabledFields = array('hiddenfield1', 'hiddenfield2', 'randomfield');
Not validating a form at all

If you have Security enabled, but do not want it to validate a certain form, you would set validatePost to false in your beforeFilter(). This is MANDATORY if you are doing any type of Ajax requests.

if ($this->params['action'] == 'actionName') {
	$this->Security->validatePost = false;
}
// Ajax requests
$this->Security->validatePost = false; 
if (!$this->RequestHandler->isAjax()) {
	$this->Security->blackHole($this, 'You are not authorized to process this request!');
}

And that is all, simple isn't it? All it requires is you adding the SecurityComponent to your controllers $components and a bit of magic on your end.