How can I protect users from cross-site request forgeries using the Zend Framework?
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 fr开发者_JAVA技巧om 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?
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
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.
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);
精彩评论