csrfpearquickform

PEAR QuickForm2 CSRF Protection


I was looking for a way to ensure CSRF-Protection in my Quickform2.

I found this link but it's for QuickForm1.

Any ideas how I can adapt this to QF2?


Solution

  • After some fiddling around I came up with this solution.

    Maybe it helps someone else as well:

    <?php
    
    /**
     * @uses HTML_QuickForm
     * @desc Add automatic CSRF mitigation to all forms by incorporating a token that must be matched in the session and forcing the use of POST method
     * Based on: http://www.zapoyok.info/2010/07/17/csrf-et-quickform-de-pear/
     */
    require_once "QuickForm2.php";
    
    class HTML_QuickForm2s extends HTML_QuickForm2
    {
        /**
         * @property string $_sessionTokenKey The name of the session variable containing the token
         */
        private $_sessionTokenKey;
    
        /**
         * @method __construct
         * @desc Override the method to always use post and pass it on to the parent constructor. Create a session key for the token based on the form name.
         * @param $id
         * @param string $method
         * @param mixed $attributes
         * @param boolean $trackSubmit
         */
        public function __construct($id, $method = 'post', $attributes = null, $trackSubmit = true)
        {
            $this->_sessionTokenKey = "QuickForm2s_" . md5($id);
    
            parent::__construct($id, $method, $attributes, $trackSubmit);
    
            //A token hasn't been created so do so
            if (!isset($_SESSION[$this->_sessionTokenKey])) {
                $_SESSION[$this->_sessionTokenKey] = md5(uniqid(rand(), true) . session_id()); //requires the session id to be known in order to add extra difficulty to compromising
            }
    
            //Hide the token at the end of the form
            $this->addElement("hidden", "qfS_csrf");
    
            $qfsCsrf= $this->getElementsByName('qfS_csrf');
            $qfsCsrf[0]->setValue($_SESSION[$this->_sessionTokenKey]);
        }
    
        /**
         * @method validate
         * @desc Check if the passed token matches the session before allowing validation
         * @return boolean
         */
        public function validate()
        {
            $submitValues = $this->getValue();
    
            //The token was not passed or does not match
            if (!isset($submitValues['qfS_csrf']) || $submitValues['qfS_csrf'] != $_SESSION[$this->_sessionTokenKey]) {
                $this->setError("Anti-CSRF token does not match");
            }
    
            return parent::validate();
        }
    
    }