views:

194

answers:

3

My program uses Zend Framework, and I wanted to protect users from CSRF using Zend_Form_Element_Hash. But It doesn't seem to work.

For example, my code for Logout Form is

    $exithash = new Zend_Form_Element_Hash('hihacker', array('salt' => 'exitsalt'));
    $this->addElement($exithash);

In my Auth plugin for Controller I do

    $exitForm = new R00_Form_Exit();

    if ($exitForm->isValid($_POST)) {
        R00_Auth::logout(); // a wrapper for Zend_Auth::getInstance()->clearIdentity();

        Zend_Registry::get('Log')->info('User has logged out');

        $this->setRedirect($request); // redirect to the current page
    }

And in my layout

    echo new R00_Form_Exit();

Okay. But it doesn't work, I click on submit button of the form, the page reloads but the identity still exists.

As I realized, Zend_Form_Element_Hash generates new hash value for each time form creates and сompares hash from user with the hash from session - the last generated hash! It's very strange. Even if I try, for example, create only one R00_Form_Exit in my application, store it in Registry and echo from it, opening a page from my site "in a new tab" will cause all such csrf-protected forms to stop working.

So, how do I protect?

+1  A: 

You should check if the Zend_Form_Element_Hash is able to save the hash into a Zend_Session_Namespace. This is the expected behavior according to the documentation of that element: Zend Documentation

Sebastian Hoitz
How do I check? I have no clue :) Btw, I use Zend_Cache with default adapter, which uses Zend_Session, and it works fine. So I think there is no problem
valya
Just try to dump $_SESSION, which should contain all values from all Zend_Session Namespaces. If there somewhere is a value from the Hash Form element check if it remains the same across both requests.
Sebastian Hoitz
No, it isn't the same :( ["Zend_Form_Element_Hash_exitsalt_hihacker"] ["hash"] was "66cb145466361f62c7cf22899cc8807e" on one page and "405a7413ff1b58ae966dbfac4a1b9f16" on another
valya
In the code of ZF_Element_Hash: $session->setExpirationHops(1). Yeah, it regenerates the hash each time! Er, either it isn't usable or I don't get an idea
valya
A: 

Here I propose my own solution. But it still is not very cool, so I will be happy to hear any other, better solutions and comments about this.

I generate a hash for each user from it's password hash and other params. The hash is constant for every user until he change his password:

   public function getSecurityHash() {
          return md5( $this->getIntID() . $this->getLogin() . 'R00SuperSalt' );
   }

And, in the form:

    $exithash = new Zend_Form_Element_Hidden('exithash');
    $exithash->setValue( R00_Auth::getUser()->getSecurityHash() )
             ->addValidator('Identical', false, array(
                R00_Auth::getUser()->getSecurityHash()
              ))
             ->setDecorators(array('ViewHelper'))
             ->setRequired(true);
valya
$exithash->setValue( R00_Auth::getUser()->getSecurityHash() ) ->addValidator('Identical', false, array( R00_Auth::getUser()->getSecurityHash() ));Doesn't this completely destroy the effect of hashes? I mean, it is likeif("foo" == "foo")
Sebastian Hoitz
No, it doesn't :) The code is in the init() of the class. In the ->isValid(), all input from user populates into values of form, so ->setValue() affects only value in the hidden element
valya
This is not CSRF! CSRF token must be different on per-session basis (not sure, but possibly even on per-request basis).
Tomáš Fejfar
can you tell me more please?
valya
+1  A: 

It is supposed to be different, every time you call on the hash generator, in the SESSION namespace and location that you have specified. That's why you only create a hash when you are building the actual form. Doing so stores the hash for one hop (meaning page load) and then forgets it (especially) if you regenerate a form for a user. This is the purpose of CSRF! To prevent form hyjacking by invalidating out of date forms. (basically)

If you are unable to "verify" a form because of the hash changing every time, you are performing the task in the wrong order and need to re-evaluate your process.

Kevin Peno