zen of coding

Make your CakePHP forms a lot more secure

Update: 11/06/2008
Tarique Sani pointed out that I had an extra line of code, which wasn’t necessary to make all this work (perhaps an old habit, but the post has been modified to reflect the change).
———————

My recent post started up some good conversations and I figured that a good follow-up would be an example of how to use the Security component to make your forms much more… secure.

We’ll start with a basic usage and then expand a little…

First let’s assume a basic model:

class User extends AppModel {

  var $name = 'User';

  var $validate = array(
      'name'=>array('rule'=>'notEmpty'),
      'email'=>array('rule'=>'email'),
      'password'=>array(
              'Cannot be empty' => array('rule'=>'notEmpty'),
            'Must be at least 4 chars' => array('rule'=>array('minLength', 4))
      )
  );
}

We’ve got some basic validation rules defined (and note, there is no ‘required’=>true, which was the point of confusion and problems for some people).

Now we’ll assume that we have an evil user, who wants to tamper with our form fields and bypass the ‘name’ field, for example, to save blank data into the DB. If we leave our form unsecured, it is very easy to remove the field from the data array (by using some basic hacking tool) and since it won’t be present in the array, the validation will be skipped and the form data will be saved with a blank name.

So how can we avoid this problem, by using the Security component?

Let’s build our controller:

class UsersController extends AppController {

    var $name = 'Users';
    var $components = array('Security');

    function add() {
        if(!empty($this->data)) {
            $this->User->save($this->data);
        }
    }
}

And a basic view for the add action:

echo $form->create();
echo $form->inputs(array('name', 'email', 'password'));
echo $form->end('Register');

Pretty simple, right?
We’ve made our form quite secure with only one “extra” line of code:
var $components = array(‘Security’);

Let me explain exactly what happens behind the scenes…

The Security component will create a hash based on the form fields produced by our Form Helper. If someone tampers with the form fields (by adding or removing or changing any field), the hash is not going to match with the expected one and the add() action will fail.

Yep, it’s that simple. You are welcome to try to mess around with the form by using your favorite POST-modifier tool (maybe: https://addons.mozilla.org/en-US/firefox/addon/1290, thanks to Jonah for providing the link).

You’ll notice that the action will fail without any error message (you’ll just get a blank screen in most cases). In my opinion, that’s just fine for any evil user… why make the app user-friendly for them?

Well, let’s be nice and make it a bit more user-friendly. It’s a good exercise, if anything…

First, we’ll extend our controller a little by creating a beforeFilter() method, let’s add one more line of code to it:

  function beforeFilter() {
        $this->Security->blackHoleCallback = 'fail';
  }

The $this->Security->blackHoleCallback = ‘fail’; tells the Security component to call our custom fail() method, in case the action gets black-holed, which it will if someone tampers with the form.

So let’s create the fail() method:

 function fail() {
        $this->cakeError('youSuck');
    }

Alright, as you can see this method will in turn trigger the youSuck() error method, which we’ll need to build in our custom app_error.php handler:

So, if you don’t have one, create app_error.php in your /app/ root directory (this is where you define custom error methods, or override existing ones).

class AppError extends ErrorHandler {
    function youSuck() {
        $this->controller->set(array(
             'name' => __('You are an evil person', true)
         ));

        $this->__outputMessage('bad_user');
    }
}

And now we need a new view bad_user.ctp, which is placed in /app/views/errors/bad_user.ctp

<h2><?php echo $name; ?></h2>
<p class="error">
  <strong><?php __('Error'); ?>: </strong>
  <?php  __('Do not try that again!'); ?>
</p>

Now, when someone tampers with the form, they’ll get a nice error page suggesting what we really think of hackers.

Hopefully you can see how Security component can make your app a lot more secure with just 2-3 extra lines of code. And at the same time we’ve covered a little example of how to best handle custom errors.

P.S. You can make your form even more secure by supplying additional params to save().

  • An interesting topic, and well explained too. This is definitely going to be in my future releases..

    Keep it up! ;-)

  • @dr. Hannibal Lecter

    Thank you ;) … will do!

  • Reen

    The security compontent has the drawback, that it can’t handle the case, when a user opens multiple tabs with (secured) forms. Then only the most recently opened tab will be able to send POST requests. All other forms will recieve an error.

  • @Reen

    Not sure if it’s a drawback, or a good way to further increase security ;)
    Seems like an unrealistic scenario.

  • Pingback: Signets remarquables du 23/10/2008 au 05/11/2008 | Cherry on the...()

  • Hmm.. I did not know of this method. Interesting. I do not however like the idea of only one form working at once though (as Reen mentioned). Neither I do not find hashes to be “elegant” but I may be picky. Thanks for the great post though, I have learned more than one thing from it. But I do hope the cake team implants another way to handle this that does not require hashes.

  • @Jonah

    Good to hear it helped in some way :)
    Do you have another suggestion? I don’t see any other “elegant” way to make it work…

  • Will it work with javascript generated fields?

  • @José Lorenzo

    I assume it only works with the form helper, but there’s only one way to find out for sure ;)

  • Are you sure that $this->Security->requireAuth(‘add’); is needed to create a hash field? Last I saw just including the component was enough for the hash field to be generated and tested….

  • Martin Westin

    Just wanted to say thanks for a great post.
    There can never be enough guides and tutorials on security.

  • So, there’s confusion over the ‘required=>true’ in the validation, but we shouldn’t be confused by the Security->requireAuth() having nothing to do with the Auth component?

    I’m all about learning a framework/language and understanding it (and its conventions — over configuration), but reading that did crack a smile from me this morning. None-the-less, great write up and I (re?)learned something important, so thanks as always teknoid!

  • @Tarique Sani

    You’re absolutely right. Don’t know why or how I’ve overlooked that, must’ve been an old habit :)

    @Martin Westin

    Glad to hear, you’re welcome ;)

    @Brendon Kozlowski
    Turns out this was an oversight on my part, actually it is not required for the Security component to work correctly (at least for this purpose), to do what’s described in the post. So… less confusion, less code and more security after all ;)

  • Hey teknoid, great post.

    Just to clarify, adding $this->Security->requireAuth(’add’); adds a different type of form security. By default (without calling any methods) the Security component will make forms generate a hash to ensure that they haven’t been tampered with. Adding requireAuth(), on the other hand, writes a random hash to the session, which also gets written into the form. On POST, these hashes are compared. This protects the form from CSRF attacks, and is the only type of protection that interferes with Ajax or multiple tabs.

  • @nate

    Thanks, that definitely clears up a few things…
    Guess, I’ll do another follow up to clear up the confusion.

  • Cheers teknoid!

    Great post!

    Dan

  • @Daniel Vecchiato

    You’re welcome ;)

  • Pingback: Как сделать формы в CakePHP более безопасными | Bunyan.Ru()

  • Mohammadreza

    but if debug set to 0 it’s not work, any idea??

  • @Mohammadreza

    Debug setting should have no affect on it. What exactly doesn’t work?

  • I always wanted to know a simple way to get around post-modifiers.

    Truly awesome work, once again!

  • @Joe Critchley

    Cheers ;)

  • Actually, does anybody know if it saves any default hidden input values in the hash? For example, if I need the record’s ID in a hidden field?

    Surely that would save me checking it again when $this-data is posted…?

  • @Joe Critchley

    If you do $this->SomeModel->read(); and have appropriate hidden field name SomeModel.id, it will automagically populate the data for you (for example on an edit() action).

  • Fartzilla

    Hi Teknoid

    Thanks for this post, it finally cleared up why I kept getting a blank screen after many hours of searching for an answer!

    So now I know that the error is being thrown by the hash in the Security component (yep, the blackholecallback was invoked), but I can’t work out why the hash isn’t matching. I have a simple form with multiple fields, am *not* modifying it in anyway and yet everytime the post is submitted this hash mismatch error occurs.

    Do you know what could be causing a hash mismatch apart from deliberate tampering?

    Cheers

    Fartzilla

  • @Fartzilla

    It’s very hard to guess what could be the problem. If the only thing that makes the difference is the fact that you’ve added Security component, I can’t really imagine why it would happen. If you also have Auth or use Ajax, etc., there could be other reasons.

    Best to ask at the google group or IRC channel.

  • Vicer

    hi i am new to cake. but i am wondering why you said ‘there is no required => ‘true’. going through the definition of this function it seems to work as ‘isset($data[‘ModelName’][‘fieldName’])’. so theoretically, if an evil user, has tampered the post data and removed a form field, the model should be able to identify that the field is missing in the validation. But I don’t know whether it’s something wrong with my code, i am using cake 1.3 and “required => ‘true'” seems to have no effect. I can tamper the post data and simply remove a field and the post will be saved without any trouble. can you please explain why?

  • @Vicer

    No, it should not work like that… If the field is not in the data array, the validation should fail. Maybe there is something else going on…

  • hello buddy,

    I have a model called Users.

    I ahve created another action for this to just change the password. called ‘password’

    I ahve created a form to change the password where the validation occurs this way

    1. checks of the password entered is min char in length.
    2. it checks via a function if the password entered twice matches.

    in the Password action if the validation fails,

    from
    users/password/2

    i am being redirected to
    users/edit/2

    instead of staying in the password action.

    i am not sure why this is happening.

    Thanx in advance.

    Harsha M V

  • @Harsha M V

    Sorry never seen this happen before. Check to make sure the form is posting to the right place. Also, does that only happen when you enable Security Component?

    • i havent activated the Security Component. are u asking me to activate it n try ?

  • @Harsha M V

    Well, you are asking me in the post about Security component. I figured there’s a chance it might have have to do something with it…

    Anyways, as always, double-check on the friendly IRC channel or the google group.

  • Hi, good day. Wonderful post. You have gained a new subscriber. Pleasee continue this great work and I look forward to more of your great blog posts.

  • Pingback: Simple Security in CakePHP | Tony Thomas()

  • Montu Pepavanshi

    This Method is not working Boss.. change the code

  • @Montu Pepavanshi

    It’s working fine, Boss ;)

  • Atul

    hello everybody,

    Nice tutorial, lets try it….

  • MKunert

    Thanks Teknoid! Just a reminder that this article is still being found after all those years, and the code is still being put to good use. What’s 26 months in cake years? An eternity …
    Funny nag: please update to CakePHP 1.3 – there’s no __outputMessage() in ErrorHandler any more …

  • Ian

    what i’ve been trying to check if Security component will block user from re-submitting the form. I tried and it was blackhole’d but when I refreshed the form, the token id was renewed too and when submitted it successfully get through again

  • teknoid

    @Ian

    Security component is not meant to do what you need.
    Typically you disable cache and submit button.
    Once the form is submitted and your app is aware of that, when the person tries to refresh the page you can render a different view saying that “your form was already sent”.

  • Ian

    Thanks Teknoid. I just realized I was using a session variable and cleared it after saving all data. So I just used it as validation before saving. Which was much easier than i thought.

  • Thanks Teknoid

%d bloggers like this: