RSS
 

Posts Tagged ‘Security’

Working with passwords in CakePHP

25 Aug

The basics

Usually, this is already well known. But.. there are still developers who actually store the password unhashed. You always have to store user passwords hashed. You can either use the still very common sha1 method (which is the cake default as for right now) or switch to more secure measures like bcrypt.
If you want to be on the safe side you not only use salts (which is also cake standard) but also a unique salt per user. In case someone actually hacks your DB the information he gets is then usually pretty useless. You can read more about this here.

That also means that the password is a one-way ticket. Due to the technical details on hashs you are not able to recover the password from this hash – hence you always need to display some empty password field which is not prepopulated (see the examples below on how to unset the field from the controller). Most websites also use a confirmation field (second password field below the first one) to make sure, there are no spelling mistakes – since the password fields shood be unreadable using the well-known stars (input type=password).

A clean approach for working with passwords

Most beginners make the mistake to use the raw baked templates and "password" as fieldname for the database field "password" in forms.
But this causes quite some issues. For starters it will also hash and save empty strings (no input) and mess up previously entered passwords.
So you should never use this field in the forms (except for login, of course).
Always use "pwd" or some other temporary field name for your forms which you will then map to the real field in your model’s beforeSave() method:

public function beforeSave($options = array()) {
	parent::beforeSave($options);
	if (!empty($this->data[$this->alias]['pwd'])) {
		$this->data[$this->alias]['password'] = AuthComponent::password($this->data[$this->alias]['pwd']);
	}
	...
	return true;
}

Do not use isset() in this case! It’s bad to accidentally hash an empty string here (if for some reason the field was actually not null). It won’t be noticeable afterwards when the empty string is suddenly a long hash value. So better don’t do that in the first place. !empty() suffices.

Also make sure that you grab and use the password before it gets hashed if you plan on sending an email with the password to the user.
After the hashing there is no way in resolving it back to what it used to be (basic principle of hash algorithms).

The main advantage of ALWAYS using a different name (even with cake2) is that you cannot accidently write an unhandled/unhashed password to your database.
Also consider providing a "pwd_repeat" field to ensure the entered password was typed correctly – especially on the user side.
To keep this easy and extendable use the following behavior.

For your validation rules you will need a few new methods like validateIdentical (to ensure the password and the confirmation password match) as well some rules on min length etc (more on this and some code in the next section as well as the linked behavior code).

If you use passwords in multiple places (or just use password stuff in multiple apps) it might make sense to separate the code in a clean way. See the next chapter.

Note that in newer versions of CakePHP (>= 2.4) it is recommended to not use the AuthComponent, but the hasher classes directly:

// At the top of the file
App::uses('SimplePasswordHasher', 'Controller/Component/Auth');
// Inside your User model
public function beforeSave($options = array()) {
	parent::beforeSave($options);
	if (!empty($this->data[$this->alias]['pwd'])) {
		$PasswordHasher = new SimplePasswordHasher(); // or add array('hashType' => '...') to overwrite default "sha1" type
		$this->data[$this->alias]['password'] = $PasswordHasher->hash($this->data[$this->alias]['pwd']);
	}
	...
	return true;
}

For new apps you should at least consider using Blowfish, though.

Note: The most common mistake with more safe hashing algorithms is to use too short DB fields. Make sure your password field is of at least the length of the hash (for blowfish up to 123). So it is wise to simply use VARCHAR(255) and play it safe. This also leaves room for even "safer" hashes one day.

Introducing my Passwordable behavior

The code can be found on github (CakePHP2.x) [1.3 – careful, different name back then].
What it does is taking care of all the stuff that usually is done redundant in several places: hashing, validating, whitelisting, ensuring fields, …
Using the behavior the controller code stays slim (remember: fat model, slim controller). Especially for beginners this really helps to keep it clean and safe. But I use is everywhere to keep it DRY. In case I have to adjust I usually only have to touch the behavior code.

The most important fact the behavior automatically takes care of is the update process.
If you have the password field in your form and you don’t enter anything it will just skip the password changing part. As soon as you enter something the behavior assumes that you actually want to change it then. Usually this has to be covered in the controller or model at some point. This is also the point most beginners struggle with. The overhead is reduced to the behavior as responsible element.

You can override the default values with Configure::write('Passwordable.auth', 'MyAuth'); for example (config/bootstrap). It is also possible to change them at runtime – where you include the behavior.
It is important NOT to globally assign the behavior to the model – for security purposes.
Only use it in those actions that actually work with the password (update/reset/…). This way other actions cannot accidently use the behavior to update the password (or by tempering with the forms).

PWD_MIN_LENGTH and PWD_MAX_LENGTH are constants and can be set in the bootstrap. They are now deprecated, though, as you should be using Configure keys here, as well.

Note: Do NOT add the above beforeSave codesnippet with the manual password hashing as the behavior already does that. Or the password will be hashed twice on save (and thus be rendered unusable).

Register forms

The basic use case first. We just attach the behavior to the model and and the two password fields in the view.
It boils down to:

if ($this->request->is('post') || $this->request->is('put')) {
	$this->User->Behaviors->attach('Tools.Passwordable');
	if ($this->User->save($this->request->data, true, array('username', 'email', ..., 'pwd', 'pwd_repeat'))) {
		//SUCCESS flash/redirect
	}
	//ERROR flash
	unset($this->request->data['User']['pwd']);
	unset($this->request->data['User']['pwd_repeat']);
}

The register form then contains

echo $this->Form->input('pwd', array('type'=>'password', 'value'=>'', 'autocomplete'=>'off'));
echo $this->Form->input('pwd_repeat', array('type'=>'password', 'value'=>'', 'autocomplete'=>'off'));

Important: Be careful with your whitelisting (as it can go wrong quickly).
Either use the security component to make sure, only the right fields are passed, processed and saved, or use whitelisting appropriatly (including all form fields that should be saved!). Do NOT use it if you don’t know how.
The declaration of the save method is save($data, $validate, $whitelist) or just save($data, $options) (then including validate and whitelist as array keys in options).
The whitelist MUST contain all relevant fields coming from the controller, so for your registration form all fields including the password fields (as behaviors cannot add fields automatically to the whitelist).

In the above registration form example we add all fields like "username" or "email" – and also "pwd", "pwd_repeat" etc then, of course. The only field the behavior can add automatically, is "password", the final password field name going into the DB. Thus we can omit that here.

Admin Forms

if ($this->request->is('post') || $this->request->is('put')) {
	$this->User->Behaviors->attach('Tools.Passwordable', array('require' => false, 'confirm' => false));			
	if ($this->User->save($this->request->data)) {
		//YEAH
	} else {
		//OH NO
	}
}

Using require here is quite important. This makes the validation skip the password fields if you do not enter anything. The password will be left alone.
As admin I also might not want to retype the password. So confirm can possibly be set to false (although a confirmation field is always a good idea).

/users/edit view

Here is the most common scenario of a complete edit form for the user with only basic password input:

if ($this->request->is('post') || $this->request->is('put')) {
	$this->User->Behaviors->attach('Tools.Passwordable', array('require' => false));			
	if ($this->User->save($this->request->data)) {
		//YEAH
	} else {
		//OH NO
	}
}

You have your two input fields in your form again which can be left empty to not update the password. Simple, isn’t it?

On an edit form the user should not have to be forced to change his password. Leaving the field empty will just skip that part. As soon as the user enters a new password, though, validation will get triggered for this field as specified in your settings.

If you use whitelisting (which is a good idea) please make sure, that you set the whitelist to only those fields from your actual model that are allowed to be updated (or use Security component).

Separate password form for the user

Note: This is the form with only the password to be changed – see the other form above for a complete edit view

if ($this->request->is('post') || $this->request->is('put')) {
	// attach the behavior and force the user to enter the current password for security purposes
	$this->User->Behaviors->attach('Tools.Passwordable', array('current'=>true));
	$this->request->data['User']['id'] = $this->Session->read('Auth.User.id');
	if ($this->User->save($this->request->data, true, array('id',  'pwd', 'pwd_repeat'))) {
		//SUCCESS flash/redirect
	}
	//ERROR flash
	// pw should not be passed to the view again for security reasons
	unset($this->request->data['User']['pwd']);
	unset($this->request->data['User']['pwd_repeat']);
}

As user I have to first provide the old password and confirm (retype) the new password. Confirmation is active by default.
The advantage of such a separate view might be that you can reduce complexity for the user. Especially with confirmation of the current password or more.

Login

This is only for completeness. The login part matches the one in the documentation.
Here we use the field that matches the one in the database – so password in most cases:

echo $this->Form->input('password');

On important issue, though: For the login forms some forget to clear the password after an unsuccessful post.

if ($this->request->is('post') || $this->request->is('put')) {
	if ($this->Auth->login()) {
		...
	}
	// the important part after every post:
	$this->request->data['User']['password'] = '';
}

Now the password field stays empty. Which is usually a good thing to do in forms.
The Passwordable behavior is not needed here as the Auth component already takes care of the login procedure. That’s why "password" as fieldname is all right in this case.

Is there more?

Yes, there is. You can take a look at the behavior itself for more configuration or peek into its test case.
‘allowSame’ might be interesting. false requires the user to use a different password then the one previously entered. This can be useful on a separate "change password" page and if you want to force the user to change it to something than it was before.

Custom auth and hash types

If you derive from the default Form authenticate adapter or the default hashing type, you need to tell the behavior that. See the appropriate settings.
For cake2.4 there is now also the support for "passwordHasher" config.

Your own validation rules

If you do not provide any validation rules for your password fields, the behavior will automatically use the "best practice" ones of his own. But you can always define your own ones in the model to overwrite then (not recommended, though).

Letting users change their password

For users who want to change or have forgotten their password, check out "Tokens" and the reset functionality mentioned there.

I18n

Don’t forget to take the translation strings from the plugin’s Locale folder and translate them accordingly to your language (eng, deu, …) in your APP/Locale folder.

Common pitfalls

If some fields are not saved, make sure your whitelisting is in order or just drop it in favor of the Security component.

If you are creating a password change form and include the id in the form <?php echo $this->Form->input('id'); ?> and also check on it wrongly using is(post) only, it won’t work:

if ($this->request->is('post')) {} // Careful with this

You should always check on both put and post if you don’t know why (or use my quick Common->isPosted()).
Also, it is a good idea to keep the id out of the form and inject it prior to the save call as shown above.

Notes

In Cake1.3 the framework automatically hashes the field "password". that’s one of the reasons why you should use "pwd" or something similar and either let the behavior/model handle it or assign "pwd" to "password" manually on save.
In Cake2.0 no automatic hashing will be done anymore. But it will still be useful not to use "password". Especially because the validation rules would not apply to a sha1 string etc^^

For resetting local passwords for development purposes (or after salt/hash changes) you might want to look into my password reset shell script.

And if you want to create your first (admin) user, you might want to take a look at my User shell which saves you the trouble of manually hashing a password and running some custom sql query. just hit cake Tools.User and "bake yourself one".

Note: this article has been rewritten for 2.x (so $this->request->data instead of this->data is now being used inside the controller scope).

2013-08 Important fix

There has been an important correction to make handling of required input more stable. Please replace any 'allowEmpty' => true with the new 'require' => false.
Attaching the behavior, it will automatically require a password input for BC.

2013-10 Important update

Now, since CakePHP2.5, the whitelisting works as expected even from within behaviors. Only now we can omit all form fields that the behavior works with (pwd, pwd_repeat and pwd_current) in the whitelisting. The behavior will add them automatically, if needed.
For a password change form then, for example, it boils down to just the id now:

if ($this->User->save($this->request->data, array('fieldList' => array('id')))) {}

2014-04-02 Use Blowfish now if you can

If you are developing a new 2.x app, use the new 3.x standard right away. It is way safer and useful to use Blowfish or any of the "real" password hashing algos.
With the Passwordable behavior it is this config setting:

$config['Passwordable'] = array(
	'authType' => 'Blowfish'
);

All you then need to do is to adjust your AppController:

public function beforeFilter() {
	$this->Auth->authenticate = array(
		'Form' => array(
			'passwordHasher' => Configure::read('Passwordable.authType'))); // Or simply `Blowfish`
	parent::beforeFilter();
}

To migrate live applications with existing users and password this is a little bit more tricky.
There is an open ticket regarding that issue – and some MultiHasher class will probably serve best.
If would first try to match the new algo and if not possible will try the deprecated algos and maybe even rehash it to the new one if possible. Sooner or later all
active accounts should have a up-to-date blowfish password.

2014-07-08 Backported PHP5.5+ password_hash()

The new PHP5.5+ password_hash()/password_verify()/password_needs_rehash() functions are now available in 2.x via Shim.Modern PasswordHasher class. I backported the 3.x DefaultPasswordHasher class for this.
All that is needed, is to add this new hasher to the Auth and Passwordable configs:

'authType' => 'Blowfish',
'passwordHasher' => 'Shim.Modern'

See the test cases for details.

You can also pass in options (like "cost"):

'passwordHasher' => array('className' => 'Shim.Modern', 'cost' => 20)

And don’t forget to add the password_compat shim via composer or the included version of it via require if you are not yet on PHP5.5, but PHP5.4 etc:

// Without composer - in your bootstrap.php
require CakePlugin::path('Shim') . 'Lib/Bootstrap/Password.php';
// With composer
"require": {
	"ircmaxell/password-compat": "dev-master"
}

To complete the new "modern" password hashing, you can also make login() automatically rehash the passwords if needed:

// In your login action
if ($this->Auth->login()) {
	$this->Common->flashMessage(__('You have logged in successfully.'), 'success');
	
	$this->User->Behaviors->load('Tools.Passwordable', array('confirm' => false));
	$password = $this->request->data['User']['password'];
	$dbPassword = $this->User->field('password', array('id' => $user['id']));
	if ($this->User->needsPasswordRehash($dbPassword)) {
		$data = array(
			'id' => $user['id'],
			'pwd' => $password,
			'modified' => false
		);
		if (!$this->User->save($data)) {
			trigger_error('Could not store new pwd for user ' . $user['id'] . '.');
		}
	}
	return $this->redirect($this->Auth->redirectUrl());
}

Note how it uses the currently stored hash with needsPasswordRehash() to determine if an update is necessary. If so it will do that automatically.
The additional check on save() might not be necessary – but in case you changed your validation params you might need to loosen the validation here then in order for the password to be rehashed without errors.

In future versions (CakePHP3.x) this will be easier as the Auth component will directly provide wrapper methods for this ($this->Auth->loginProvider()->needsPasswordRehash()).
But until then this shim works quite well in 2.x.

For the code visit the Shim plugin.

CakePHP 3.x

For CakePHP 3.x it is still quite convenient to use the behavior, even for login and rehashing. See the 3.x compatible version of the Tools plugin .
The docs for it can directly be found there in Tools Plugin 3.0 documentation

 
36 Comments

Posted in CakePHP

 

Widgets and CSRF – How To

29 Jan

Outline

Did you ever try to create a widget like "Facebook Like Box" or whatever for your website? Did you try to include it in a foreign page with some JS code?
If you use iframes, this is trivial. But nobody ever wants iframes. We want to embed it properly.
But this is not as easy as it looks.

The idea

We want to enable all users to include such a small snippet on their own websites:

<div id="onlineWidget">
<script type="text/javascript" src="http://www.website.com/widgets/online.js"></script>
</div>

Lets say, we want to include it on the website "www.example.com"

If you develop locally, you will not discover any problems. But as soon as you put it online, nothing will work anymore.

The problem

A browser cannot include JS from a foreign domain (in this case "www.website.com") in his own source code at "www.example.com" and use ajax calls with this javascript code.
He will always get an empty result due to the "Same Origin Policy". It prevents possible security risks if the JS of the foreign website could actually call any url it wants.

Same goes for iframes by the way if you want to access the foreign content from the current location. Accessing it is possible, but changing the height of the iframe dynamically (based on the lenght of content) is not – because it would allow "editing" of the foreign iframe content.

Locally, it is always "localhost" as domain. So no problem there. On 2 websites in the internet, the domains will be different. Therefore you might be able to request something, but the browser will just omit any result.

The solution

We don’t use AJAX but dynamically rendered JS.
That’s possible because we can add JS files to the current DOM and the browser will open and execute the new JS code – or at least has access to it.

So we can use something like Hironori’s "JSONscriptRequest", which can be found here.
It uses dynamically generated script tags and some JSON.

Basic concept
It’s fairly simply: Trigger a click event on buttons and links which are caught in the included javascript. Build an url and submit it to the JSONscriptRequest script. This will then add the script into the html dom. With this technique you can dynamically add just about anything on any website.

Example
We might want to add a "name filter" in order to find only relevant friends who are online right now.

// inside document.ready()
jQuery("#filter-name").change(function() {
  	refreshContent(null);
  });
function refreshContent() {
	...
	aObj = new JSONscriptRequest(href);
	aObj.buildScriptTag();
	aObj.addScriptTag();
}

and a little script to catch the inserted data and put it in the right space of your widget:

function result(jData) {
	if (jData == null) {
		alert('There was a problem parsing');
		return;
	}
	// update div
	content = jData.content;
	jQuery('#onlineWidget').html(content);
}

How does it work?
When we include the widget from above we tell the browser to include a foreign javascript file. This itself can be a dynamically generated js file (depending on the url, the foreign server triggers specific actions and – with .js as extension – renders it as JS content. It now contains the desired content (sorted by name or whatever).
This content is embedded in the html of the website where we want to display the widget. Now we can access the javascript variables inside this new script and update the div container.

Yes, it can be dangerous

The author if this little script has put some warnings in his scripts:

A SECURITY WARNING FROM DOUGLAS CROCKFORD:
“The dynamic script tag hack suffers from a problem. It allows a page
to access data from any server in the web, which is really useful.
Unfortunately, the data is returned in the form of a script. That script
can deliver the data, but it runs with the same authority as scripts on
the base page, so it is able to steal cookies or misuse the authorization
of the user with the server. A rogue script can do destructive things to
the relationship between the user and the base server.”

So, be extremely cautious in your use of this script.

Yes, it is avoiding the security measures set to prevent CRSF. But in this case we want to allow totally harmless widgets to work. The downside is, that you never know what pages contain harmful and what contain harmless widgets…

 

Preventing Brute Force on Login

11 Nov

With default cake login procedures a user could try unlimited passwords to one specific account.
That means, if you write a bot that tries every possible combination (thousand times per minute!), this bot could eventually gain access to the account. To cloak it, he could use several bots on several different locations or proxies.

Some websites use the session to count the attempts and display a captcha after x failures. Or they use the IP address to block this user for a few minutes. Both is not very secure.
A user could change his session and his IP frequently (depending on the skills of the attacker of course).
Then he would still have unlimited trials.

More secure mechanisms

Just some ideas to discuss here – i didn’t yet test the ideas in real life scenarios.

I don’t like the captcha stuff. I would prefer the login timeout. Either way we need to make sure that everybody – no matter if human, bot or both – gets the same result.

Lets say, our "attacker" targets "benni123" and wants access.
He tries several combinations and suddenly he cannot login for a few minutes. No matter what he does.
How?

My idea: use the database for it. Store the information based on the record the user wants to validate against.
Every failure the database stores it with the User.user_id of "benni123" and the time-stamp.
If the amount is greater as 5 for example, the login is always aborted for this user account. The real user cannot login as well, of course. But after a few minutes everything is fine again – and he even knows somebody tried to access the account.

No bot would ever find the right key. Its just not reasonable to write a bot that only tries to access the account every few minutes. It would need hundreds of years with medium-sized passwords.

You could also display a captcha (google does it this way) after x failures. But they "could" be cracked using a specific captcha reader for your site. once this is in place, they provide not much of security anymore. Meaning: The captcha must be complicated enough for the bot but still readable for humans. reality shows that this is not easy to accomplish. Therefore I tend to use captchas only to slow too active users down (prevent spamming, message flooding, etc).

Additionally we want a php script timeout (wait x seconds to respond) after unsuccessfull attempts.
This way the user doesn’t really notice but any brute force script would need remarkably longer for a chain of attempts.

In the end we could do this:
Display a captcha after maybe 5 wrong trials. After another 5 correct captcha inputs but incorrect password inputs (important! we dont want this to happen if the catpcha is wrong – otherwise an attacker could easily shut down all accounts) shut the account down for a specific timeout. Of course, if the valid user tries to access the account in that time he needs to get a proper error message.

Implementation

Using the timeout method we extend the core auth component:
Component AuthExt:

/**
 * override auth login
 * @override
 */
function login($data = null) {
	if (!$this->loginExt($data)) {
		# security timeout?
		sleep(1); # 1 second for right now
		return $this->_loggedIn;
	}
	$model = $this->getModel();
	// do final stuff (getting roles and additional session infos, raise login counter, ...)
	return $this->_loggedIn;
}
function loginExt($data = null) {
	$this->__setDefaults();
	$this->_loggedIn = false;
	if (empty($data)) {
		$data = $this->data;
	}
	# check against database entries if user is temporary banned
	if ($this->floodProtectionActive($data)) { # TODO: implement
		return $this->_loggedIn;
	}
	if ($user = $this->identify($data)) {
		// here we can check if other reasons prevent returning true (suspended, account not active yet, ...)
		return $this->_loggedIn;
	}
	# seems like we need to log this attempt as a failure
	$this->floodProtectionUpdate($data); # TODO: implement
	
	return $this->_loggedIn;
}

Security Layers

Level 1: Javascript (Timeouts, …)

  • LOW!
  • Can easily be bypassed

Level 2: Captchas, IP/Session based login monitoring, Sleep-Timeouts

  • MEDIUM
  • Can be spoofed/hacked (with some expertise anyway)

Level 3: Database based monitoring (Errors per user and login timeouts)

  • HIGH
  • Can be used against members (blocking access for valid users)

Final words

You would need a garbage collector for the database table. The last x+1 records are more than enough (except you want to debug the information or transform them into statistics).

Using only captchas:
Assuming that captchas are still as secure as we would like them to be, this is the easiest implementation.
After x failures, an additional captcha has to be entered.
The real user would get a captcha right away, if an attacker just tried to access his account. So no delay at all.
The bot, though, should fail due to the captcha in place.

Using a script timeout:
As mentioned above, the script timeout (sleep command/function) is really helpful in order to slow down brute force attempts.
A few seconds are more than enough. Normally 100 attempts need 5 seconds, with this timeout they will need more than 200.
Assuming that the attacker doesn’t use 100 parallel connections, of course.

Using a block timeout:
The real user could be identified by cookies, specific browser fingerprint etc.
This in combination with the correct password in the first place could override the above protection mechanism. allowing the user to enter EVEN if the timeout is in place due to too many failures.
The attacker should not be able to guess the fingerprint, though.
Maybe the "browser user agent" combined with a cookie information (maybe even with the IP, although it could already have changed). Even if the attacker knew that both are used for the override, he would have too many combinations ABOVE the main problem: the correct password.
He would need to know the content of the encrypted cookie as well as the exact browser version used from the real user the last time he logged on.

Combining them:
This way it is most secure on application level. Even if a working captcha reader is found for your captchas, the attacker will not be able to brute force login.

One advantage of the database log tables:
You can easily monitor the fails and get a pretty good picture how many "brute force attacks" have actually been commited on your website.

What do you think? And is the possible "override" maybe the key for vulnerabilities again? At least for a specific target this might be plausible.

 
4 Comments

Posted in CakePHP

 

CakePHP Security

05 Oct

When I first started with CakePHP I didn’t know about those things, either.
Everything was new and I was overwhelmed by all the functionality of the framework.

The basic rule is: Validate input, escape/sanitize output

Input

I already wrote an article about Saving Model Data and Security. It covers some aspects of the saving process.
You are even quicker if you include the Security Component in the app controller. This automatically takes care of form manipulation and other things like Cross-Site Request Forgery (CSRF).

Cake already takes care of the usual risks by escaping all queries (except for the manually built ones).

Some use Sanitize or other things prior to the save to ensure that the content is stripped of any harmful strings. I dont think this is a good approach. At least not in general. Usually the user wants to post something which intentionality consists of some special characters. He would be quite upset if all kinds of things are stripped out just because the developer felt like it. It’s overkill and unnecessary for text input.
On the other hand, if you allow HTML (with a wysiwyg editor?), you cannot escape the output. Therefore it makes sense in this case to protect your site with something like "htmlpurifier".

My opinion is: ONLY validate, do not sanitize or strip things off the input. You have plenty of time to do that on output (see below).

Output

The other part is the view and potential risks from xss attacks etc.
It is actually simpler than you might think. All CakePHP helpers already escape by default ($this->Html->link() for instance).
If you output database content directly in the view you need to escape those text strings manually, though.
This is done with the h() function (conv. function of htmlspecialchars):

echo h($model['Model']['comment']);

No matter what the user posted, it is now harmless (javascript, xss stuff).

So to sum it up: Escape all stringish values coming from DB via h() (varchar, text, …).

Many forget that even image titles or alt attributes must be escaped. So if you don’t use the helper for it, escape manually. A good testing environment has always some of those "bad strings" in the database. Just insert some of the first examples on this XSS site. If you don’t get "alerts" in your views, you know that you did everything right.
I once wrote a lib + shell script that extracted all those xss strings (xml is available) and automatically added them to the specified model. After calling the "index" overview I found out that nothing happened – which is always a good sign in this case.

Flash messages are often forgotten, as well. If you use user input in them, escape it:

$name = $this->data['Model']['title'];
$this->Session->setFlash(sprintf(__('record \'%s\' saved', true), h($name))); // prints "record {title} saved"

POST/GET

To protect yourself from a very unpleasant attack which can make you delete all kinds of content, you should always ensure that database modifications (INSERT, UPDATE, DELETE) can only be triggered by POST actions (ajax or not ajax). This is not for no reason the specification for HTTP. "GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval".
Currently – using the default bake templates – you would allow DELETE simply by calling "/controllers/delete/ID".
So you might want to customize your scripts. Use a form submit button to post to the url (or jquery) and blackhole the request if it is a GET request.

What can happen if you don’t follow the specification? If you don’t allow any HTML probably nothing. But let’s assume you allow BBCode or HTML – both properly sanitizes/cleaned.
I will give you one example with "comments" on a blog post. The result of the user comment might be:

<b>Blablabla</b>, <img src="/admin/users/delete/3" /> FooBar.

Normal users will just see a scrambled image (because they don’t have the admin role). The url can only delete a user if you are currently logged in as an admin.
Note: An image tag ALWAYS assumes that the src is valid. It will call anything you put in there as url. Even urls that aren’t any image. So the url redirects inside the image tag (no access without admin role).

Ok, but now the admin logs on and checks out the post. Well, he DOES have the right to delete users. And therefore the img tag automatically deletes the user (because GET requests are allowed). The admin doesn’t even notice. Combine some of those delete actions and you can really mess things up. Especially if cascading is on and all kinds of user related content (users hasMany posts, …) is removed as well.

Miscellaneous

Be especially careful with user submitted data or $_GET params if they are about file names, urls or similar system related information.
They can have ../../ in them which could lead to vulnerabilities.

Webserver

If you host the website on a managed server or even a root server, you need to make sure that the server is secure. That includes the following:

  • Keep the packages up to date (“apt-get update” + “apt-get upgrade”)
  • Register Globals OFF, Magic Quotes OFF (Cake handles this!)
  • Hide your exact server version with ServerSignature Off

Spamming

Meaning: Overloading a system through excessive traffic can lead to denial of service for other users or system failure.
This can happen if you allow a bot to post public accessible forms (registration etc). He could sign up millions of users in just a few hours filling your database tables to the outer limit. You can use timeouts or captchas to prevent this (careful: try not to use session based stuff because bots change their session all the time).
Timeouts also prevent "brute force / dictionary attacks".

Another cake-related overloading attempt: "limit" on pagination. Anybody who knows that CakePHP is used, could simply append "/index/limit:1000000". If there actually are that many entries it will most certainly cause excessive traffic. Currently you have to implement this yourself. Ticket still not resolved!

Conclusion

That’s it, basically.
As you can see it is not that difficult to secure your app.

Tip: Make use of baking scripts and custom templates! This way you got h() and other security measures right in the baked views. That’s a real timesaver.

There are some other aspects I did not yet mension: session hijacking etc. Maybe I catch up on that later on.
Anything else I might have missed?

Security Update 2013-06

There have been some security vulnerabilities that have been discovered in the recent past. It is vital that you upgrade your core to at least version 2.3.6 to be on the safe side!
Also test your app from time to time against common issues like XSS, Form tempering, SQL injection etc to detect those flaws first before others can make use of them.

 
9 Comments

Posted in CakePHP

 

Saving Model Data and Security

21 Sep

In google groups there are quite a few discussions every month about security against (primary key) injection, xss or other things.
And yes, the default templates will not protect you from any of this. They are meant to produce quick "standard" output. But they are not prepared for the real world, at all.
It is actually quite irresponsible not to use custom templates or manually adjust the forms/views.
UPDATE: I could finally convince them to use h() in their default templates! A big WIN for security!

A while ago i wrote about how to create custom templates. See this article for details.
You will also notice, that the controller actions and views are protected against basic – and only basic – injections.
That is mainly xss-attacks. By using h() in the views we can make sure that dangerous strings get destroyed. The custom templates also ensure, that the record we edit/delete exists.

What it still doesn’t do is protecting the model data.

Note: some of the following could be achieved by adding the security component. But sometimes, it is not possible or not desired.

Protection against too many fields

Another tricky one. Again, Firebug can be used to violate your forms. Lets say, a user wants to have more rights – and he happens to know that User.role_id must be 2 instead of 1 in order to be "admin".
In the User edit form (besides "email", "birthday" and stuff) he could then inject a form input called "role_id" which usually is not there.
With a normal $this->save() operation this would actually work! After the next login his role would be 2 (= admin).
What can we do about something like that?

Cake has a "fieldList" as third parameter of the save() function build in:

if ($this->User->save($this->data, true, array('id', 'gender', 'birthday'))) { # note the id (needed here)

Id, gender and birthday are passed, any other field in $this->data will be omitted (especially our role_id^^).
I use this in all sensitive forms (user and role related).

Careful: If you don’t pass the id, it will be omitted and the model cannot set its primary key (and might perform "add" instead of "edit"!

Primary key injection on edit actions

This is the not quite the same as the above topics. All edit actions have the primary key in the forms. I usually remove them, but sometimes I am too lazy to do that (because you need to alter the form url then as well).
So the id actually exists – but won’t be checked prior to the save.
Well, here are some custom solutions – there are more general ones, of course. But I just want to point out the problem and a way to fix it. These could be added to your bake templates, for instance.

Lets take our custom template from above (see link) and modify it:

public function edit($id = null) {
	if (empty($id) || !($data = $this->User->find('first', array('conditions' => array('User.id' => $id)))) {
		//error flash message
		$this->redirect(array('action' => 'index'));
	}
	if (!empty($this->data)) {
		# solution 1: just override with the "correct" one:
		$this->data['User']['id'] = $id;
		# solution 2: check against "correct" one
		if ($this->data['User']['id'] != $id) {
			//BLACKHOLE
		}
		if ($this->User->save($this->data)) {
			//OK
		} else {
			//ERROR
		}
	}
}

Solution 1 is self-explanatory and cannot go wrong.
Solution 2:
After we made sure the record exists, we check the passed id against the record id.
If they don’t match someone tries to trick us. Otherwise the record can be saved.
Solution 3:
Remove the id from the passed arguments (third parameter of save() – but makes it more complicated)
Solution 4:
Put the check in beforeValidate() of your app_model.php. Usually – with the above template you have just "read" the record, so the model has the id in $this->id. It could then return false if the passed id and $this->id do not match. Thats actually my favorite. But I am not sure if it works in all cases. You may only check if you are actually saving a record – meaning: both ids have to be not empty. Only then it makes sense to apply this specific piece of validation.

Protection against missing fields

This is a tricky one. Most beginners (I didn’t either) dont realize that a form can be modified with tools like and Firebug (Firefox Addon) and that Cake would not bother.
Lets say we have a registration form and your gender has to be provided. But you don’t want to. So you simply remove this input from the form and submit it. ViolΓ  – it worked!

Why does it work? Cake automatically applies the validation "needed" for this form. All fields that are passed (it doesn’t matter if they are empty or not – they just have to exist on submit) will be checked. Validation rules for fields that did not get passed will be omitted. And thats our problem. Right here.

OK, we need to make sure that the form fields are actually part of the model data before the validation kicks in.
I wrote a app model extension which can be used – you could also write a model function which one by one adds the required fields if the field does not exist.

Put this in your app model:

/**
 * set + guaranteeFields!
 * extends the core set function (only using data!!!)
 * 2010-03-11 ms
 */
public function set($data, $data2 = null, $requiredFields = array()) {
	if (!empty($requiredFields)) {
		$data = $this->guaranteeFields($requiredFields, $data);
	}
	return parent::set($data, $data2);
}
/**
 * make sure required fields exists - in order to properly validate them
 * @param array: field1, field2 - or field1, Model2.field1 etc
 * @param array: data (optional, otherwise the array with the required fields will be returned)
 * @return array
 * 2010-03-11 ms
 */
public function guaranteeFields($requiredFields, $data = null) {
	$guaranteedFields = array();
	foreach ($requiredFields as $column) {
		if (strpos($column, '.') !== false) {
			list($model, $column) = explode('.', $column, 2);
		} else {
			$model = $this->alias;
		}
		$guaranteedFields[$model][$column] = ''; # now field exists in any case!
	}
	if ($data === null) {
		return $guaranteedFields;
	}
	if (!empty($guaranteedFields)) {
		$data = Set::merge($guaranteedFields, $data);
	}
	return $data;
}

We can now make sure removing any form field will have no effect πŸ™‚ I call it "enforced whitelisting".

Case 1: Using $this->Model->set($this->data) and $this->Model->save() – Important: note the NULL in save(), we may not pass the data a second time!

$this->User->set($this->data, null, array('gender', 'birthday', ...));
$this->User->save();

Case 2: Using directly $this->Model->guaranteeFields($fields, $this->data) and $this->Model->save($this->data)

$data = $this->User->guaranteeFields(array('gender', 'birthday', ...), $this->data);
$this->User->save($data);

Lets combine them

$this->data['User']['id'] = $id; // on edit
$this->User->set($this->data, null, array('gender', 'birthday', ...));
if ($this->User->save(null, true, array('id', 'gender', 'birthday', ...))) { # note the id (needed here)
	//OK
} else {
	//ERROR
}

Now thats protection πŸ™‚
Only the fields that you want to are passed and all the fields that should be there are there. And the primary key is the one it is supposed to be. If your validation rules are correct, you got the perfect form handling, I guess.

Blacklisting

// put this in your app_model.php
public function blacklist($blackList = array()) {
	return array_diff(array_keys($this->schema()), $blackList);
}

Although not recommended for general usage, this might be useful for some rare occasions.
It returns all fields of the current database table except the ones you blacklisted.

Usage:

$this->User->save(null, true, $this->blacklist(array('birthday'));

If you want to pass non-existing fields to the model for validation you need to array_merge() them!

$this->User->save(null, true, array_merge(array('virtual_field'), $this->blacklist(array('birthday')));

Server-side and Client-side Validation

First one is what we just discussed. Client-side usually is JS or browser-specific validation. You should NEVER rely on client-side validation – it can easily be tricked. It is nice to have it – "additionally" in order to prevent unnecessary posts, but the real deal should be what your model validation returns.

SQL Injection

I always smile about other webprojects that are vulnerable to this attack. Its just hilarious. Thats why you use a framework. Because it takes care of the basic problems. And this is one of it.
There are only very few things to be aware of:
Always manually escape manual SQL queries!
CakePHP only takes care of the queries run by $this->save(), $this->saveAll() etc. So if you ever have to write your own SQL script, you will need to do that yourself.

The myth about "required" and "allowEmpty"

They seem to be the same, don’t they? But there is a huge difference between them (the cookbook does mention it here).
"required" is like my "Protection against missing fields", only hard-coded. These fields will validate false, if this field is not present on validation. It does not matter if the field is empty or not. The word "required" only applies to the database field, not its content.
"allowEmpty" is equal to "minLength 1" and ONLY validates this field if the field has been passed to the model. If not, it will not return false. You cannot make sure that a specific field is always filled out with this parameter alone.

If you want to make sure, that the field cannot be avoided AND has to be filled out, you need to combine both rules:

public $validate = array(
	'status' => array(
		'maxLength' => array( # this can be any label you want
			'required' => true, # careful!
			'allowEmpty' => false,
			'rule' => array('maxLength', 5),
			'message' => array('valErrMaxCharacters %s', 5), # a modification of mine to allow better i18n support
			'last' => true # important for more than one rule per field!!!
		),
		... # other rules
	),
	... # other fields
);

The "careful" stands for: this can get ugly really fast. I never use "required" hard-coded. As soon as you use ajax toggling of some fields or forms which only change a small part of the record, you will get validation errors because the required fields are not present (although you don’t even want to change them). You would need to unset() those rules manually. See the above "Protection against missing fields" for a cleaner approach.

Anyway, i hope this clears up things a little bit.

If you really feel that you need to use "required" you should only use it combined with ‘on’ => ‘create’, as well. In 99% of all cases you end up with never validating "edit" validations otherwise. That’t because you often save only a part of the data where you normally wouldn’t provide those required fields.
So as a guideline:

  • Either don’t use required attribute at all and use my whitelisting approach from above
  • Use required with "on create" IF (!) the field always has to be present for all create actions. For other actions use whitelisting and guaranteeFields

A few words to validation rules in general

You can add rules for "imaginary fields", as well. The fields don’t have to exist in the database model. This way you can easily use inputs which are validated separately and combined later on (eg: pwd and pwd_repeat resulting in the final sha1-hashed "password").
Always use "last"=>true for your rules. Unfortunately, the default value is still false here. "true" makes sure that after the first rule already returned false, the following ones are not checked as well (overhead and totally useless).
Also sort your rule array properly. The rules are checked from top to bottom, so it would make sense to put all complicated stuff at the bottom and first check on simple "notEmpty" or "email" rules. The most likely ones to fail at the beginning. Why should you check "isUnique" (database query!) before "notEmpty"? Why should you check an email address exists in the database if it is invalid in the first place?

Last words

Why didn’t I use Security Component for some of the above tasks? Well, it was a bitch to use in the first months. All the time some kind of black holes (white screen of death) without any indication what went wrong. Login/Logout, some other forms as well. With the first ajax forms it got even more messy and i deactivated it at last. Works quite well without it, if you protect the forms manually. With lots of ajax stuff going on you have to do that yourself, anyway…
And against certain attacks the Security Component won’t help anyway (PrimaryKey injection in edit forms etc). So at some point you will have to deal with it yourself. You have to decide to what point. But better sooner than later. If you adjust your bake templates before you start a new project it will save you lots and lots of time while having secure forms and procedures.

Note – 2012-06-02

For Cake2.x please use $this->request->data instead of $this->data in the controller scope.

 
14 Comments

Posted in CakePHP