CVE-2020-36622 (PHP, published 2022-12-21T19:15Z)
Description: value: A vulnerability was found in sah-comp bienlein and classified as problematic. This issue affects some unknown processing. The manipulation leads to cross-site request forgery. The attack may be initiated remotely. The name of the patch is d7836a4f2b241e4745ede194f0f6fb47199cab6b. It is recommended to apply a patch to fix this issue. The identifier VDB-216473 was assigned to this vulnerability.
Severity: nan (CVSS v3 base score: 6.5)
Weakness: CWE-352 \u2014 Cross-Site Request Forgery (CSRF)
The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
Repository: https://github.com/sah-comp/bienlein
Files changed:
app/res/tpl/account/changepassword.php
app/res/tpl/account/index.php
app/res/tpl/account/login.php
app/res/tpl/account/lostpassword.php
app/res/tpl/admin/index.php
app/res/tpl/scaffold/form.php
src/Controller/Account.php
src/Controller/Login.php
src/Controller/Lostpassword.php
src/Controller/Scaffold.php
src/Model.php
src/Model/Criteria.php
Commit message: Added CSRF prevention


Vulnerable code:
        enctype="multipart/form-data">        
        </div>     
        
        
        
            }
            catch (Exception $e) {
            }    
        
    
        
                }
                catch (Exception $e) {
            }
            else {
            }    
        
    
	    Flight::render('shared/notification', array(), 'notification');
	    //
		Flight::render('shared/navigation/main', array(), 'navigation_main');
		Flight::render('shared/header', array(), 'header');
		Flight::render('shared/footer', array(), 'footer');
		Flight::render($this->template, array(
		    'record' => Flight::get('user')
		), 'content');
        if ( ! isset($_SESSION['login_id'])) {
                $login = R::graph( Flight::request()->data->dialog, TRUE );
        if ( Flight::request()->query->goto == '' || Flight::request()->query->goto == '/login' ) {
    
    
            if ( ! $user = R::findOne('user', ' email = ? LIMIT 1 ', array($this->uname))) {
            } elseif ( ! $user->requestPassword()) {
        
    
		Flight::render($this->template, array(
		    'uname' => $this->uname,
		    'message' => $this->message
		), 'content');
 		'like' => '%1$s like ?',
 		'notlike' => '%1$s not like ?',
 		'eq' => '%1$s = ?',
 		'neq' => '%1$s != ?',
 		'bw' => '%1$s like ?',
 		'ew' => '%1$s like ?',
 		'lt' => '%1$s < ?',
 		'gt' => '%1$s > ?',
 	    'in' => '%1$s in (%2$s)'
 		//'between' => __('filter_op_between'),
 		//'istrue' => __('filter_op_istrue'),
 		//'isfalse' => __('filter_op_isfalse')
 	);
     /**
      * Container for characters that have to be escaped for usage with SQL.
      *
      * @var array
      */
     public $pat = array('%', '_');

     /**
      * Container for escaped charaters.
      *
      * @var array
      */
     public $rep = array('\%', '\_');
     
     /**
      * Prepares a value according to its tag and returns it.
      *
      * @param string the value to convert
      * @return mixed
      */
     public function convertToText($value)
     {
         return $value;
     }
     
     /**
      * Prepares a value according to its tag and returns it.
      *
      * @param string the value to convert
      * @return mixed
      */
     public function convertToNumber($value)
     {
         return (float)str_replace(',', '.', $value);
         //return $value;
     }
     
     /**
      * Prepares a value according to its tag and returns it.
      *
      * @param string the value to convert
      * @return mixed
      */
     public function convertToEmail($value)
     {
         return $value;
     }
     
     /**
      * Prepares a value according to its tag and returns it.
      *
      * @param string the value to convert
      * @return mixed
      */
     public function convertToTextarea($value)
     {
         return $value;
     }
     
     /**
      * Prepares a value according to its tag and returns it.
      *
      * @param string the value to convert
      * @return mixed
      */
     public function convertToIn($value)
     {
         return $value;
     }
     
     /**
      * Prepares a value according to its tag and returns it.
      *
      * @param string the value to convert
      * @return mixed
      */
     public function convertToBool($value)
     {
         return $value;
     }
     
     /**
      * Prepares a value according to its tag and returns it.
      *
      * @param string the value to convert
      * @return mixed
      */
     public function convertToSelect($value)
     {
         return $value;
     }
     
     /**
      * Returns a mysql datetime string.
      *
      * @param string the value to convert
      * @return string
      */
     public function convertToDatetime($value)
     {
         return date('Y-m-d H:i:s', strtotime($value));
     }
     
     /**
      * Returns a mysql date string.
      *
      * @param string the value to convert
      * @return string
      */
     public function convertToDate($value)
     {
         return date('Y-m-d', strtotime($value));
     }
    
        if ( ! isset($this->map[$this->bean->op])) throw new Exception('Filter operator has no template');
    
    	switch ($this->bean->op) {
    		case 'like':
    			$value = '%'.str_replace($this->pat, $this->rep, $this->bean->value).'%';
    			break;
    		case 'notlike':
    			$value = '%'.str_replace($this->pat, $this->rep, $this->bean->value).'%';
    			break;
    		case 'bw':
    			$value = str_replace($this->pat, $this->rep, $this->bean->value).'%';
    			break;
    		case 'ew':
    			$value = '%'.str_replace($this->pat, $this->rep, $this->bean->value);
    			break;
    		case 'in':
    		    $_sharedSubName = 'shared'.ucfirst(strtolower($this->bean->substitute));
    		    $ids = array_keys($this->bean->{$_sharedSubName});
    		    $value = implode(', ', $ids);
    		    $add_to_filter_values = false;
    		    break;
    		default:
    			$value = $this->bean->value;
    	}
    	if ($add_to_filter_values) {
    	    $converter = 'convertTo' . ucfirst(strtolower($this->bean->tag));
    	    $filter->filter_values[] = $this->$converter($value);
    	}
    	return $value;
    
        if (isset($this->operators[$this->bean->tag])) return $this->operators[$this->bean->tag];
    

Fixed code:
        enctype="multipart/form-data">

        <input type="hidden" name="token" value="<?php echo Model::getCSRFToken() ?>" />


        <input type="hidden" name="token" value="<?php echo Model::getCSRFToken() ?>" />
        
        </div>

        <input type="hidden" name="token" value="<?php echo Model::getCSRFToken() ?>" />


        <input type="hidden" name="token" value="<?php echo Model::getCSRFToken() ?>" />


        <input type="hidden" name="token" value="<?php echo Model::getCSRFToken() ?>" />


        <input type="hidden" name="token" value="<?php echo Model::getCSRFToken() ?>" />



            if (! Model::validateCSRFToken(Flight::request()->data->token)) {
                $this->redirect("/logout");
            }
            } catch (Exception $e) {
            }



            if (! Model::validateCSRFToken(Flight::request()->data->token)) {
                $this->redirect("/logout");
            }
                } catch (Exception $e) {
            } else {
            }


        Flight::render('shared/notification', array(), 'notification');
        //
        Flight::render('shared/navigation/main', array(), 'navigation_main');
        Flight::render('shared/header', array(), 'header');
        Flight::render('shared/footer', array(), 'footer');
        Flight::render($this->template, array(
            'record' => Flight::get('user')
        ), 'content');
        if (! isset($_SESSION['login_id'])) {
            if (! Model::validateCSRFToken(Flight::request()->data->token)) {
                $this->redirect("/logout");
            }
                $login = R::graph(Flight::request()->data->dialog, true);
        if (Flight::request()->query->goto == '' || Flight::request()->query->goto == '/login') {


            if (! Model::validateCSRFToken(Flight::request()->data->token)) {
                $this->redirect("/logout");
            }
            if (! $user = R::findOne('user', ' email = ? LIMIT 1 ', array($this->uname))) {
            } elseif (! $user->requestPassword()) {


        Flight::render($this->template, array(
            'uname' => $this->uname,
            'message' => $this->message
        ), 'content');
            if (! Model::validateCSRFToken(Flight::request()->data->token)) {
                $this->redirect("/logout");
            }
            if (! Model::validateCSRFToken(Flight::request()->data->token)) {
                $this->redirect("/logout");
            }
            if (! Model::validateCSRFToken(Flight::request()->data->token)) {
                $this->redirect("/logout");
            }
    /**
     * Returns a "random" string to prevent CSRF.
     *
     * @return string
     */
    public static function getCSRFToken()
    {
        @session_start();
        if (empty($_SESSION['csrf_tokens'])) {
            $_SESSION['csrf_tokens'] = [];
        }
        $nonce = md5(uniqid(rand(), true));
        $_SESSION['csrf_tokens'][$nonce] = true;
        return $nonce;
    }

    /**
     * Returns wether the CSRF token validates or not.
     *
     * @param string $token
     * @return bool
     */
    public static function validateCSRFToken($token)
    {
        @session_start();
        if (isset($_SESSION['csrf_tokens'][$token])) {
            unset($_SESSION['csrf_tokens'][$token]);
            error_log('Good user');
            return true;
        }
        return false;
    }

        'like' => '%1$s like ?',
        'notlike' => '%1$s not like ?',
        'eq' => '%1$s = ?',
        'neq' => '%1$s != ?',
        'bw' => '%1$s like ?',
        'ew' => '%1$s like ?',
        'lt' => '%1$s < ?',
        'gt' => '%1$s > ?',
        'in' => '%1$s in (%2$s)'
        //'between' => __('filter_op_between'),
        //'istrue' => __('filter_op_istrue'),
        //'isfalse' => __('filter_op_isfalse')
    );
    /**
     * Container for characters that have to be escaped for usage with SQL.
     *
     * @var array
     */
    public $pat = array('%', '_');

    /**
     * Container for escaped charaters.
     *
     * @var array
     */
    public $rep = array('\%', '\_');

    /**
     * Prepares a value according to its tag and returns it.
     *
     * @param string the value to convert
     * @return mixed
     */
    public function convertToText($value)
    {
        return $value;
    }

    /**
     * Prepares a value according to its tag and returns it.
     *
     * @param string the value to convert
     * @return mixed
     */
    public function convertToNumber($value)
    {
        return (float)str_replace(',', '.', $value);
        //return $value;
    }

    /**
     * Prepares a value according to its tag and returns it.
     *
     * @param string the value to convert
     * @return mixed
     */
    public function convertToEmail($value)
    {
        return $value;
    }

    /**
     * Prepares a value according to its tag and returns it.
     *
     * @param string the value to convert
     * @return mixed
     */
    public function convertToTextarea($value)
    {
        return $value;
    }

    /**
     * Prepares a value according to its tag and returns it.
     *
     * @param string the value to convert
     * @return mixed
     */
    public function convertToIn($value)
    {
        return $value;
    }

    /**
     * Prepares a value according to its tag and returns it.
     *
     * @param string the value to convert
     * @return mixed
     */
    public function convertToBool($value)
    {
        return $value;
    }

    /**
     * Prepares a value according to its tag and returns it.
     *
     * @param string the value to convert
     * @return mixed
     */
    public function convertToSelect($value)
    {
        return $value;
    }

    /**
     * Returns a mysql datetime string.
     *
     * @param string the value to convert
     * @return string
     */
    public function convertToDatetime($value)
    {
        return date('Y-m-d H:i:s', strtotime($value));
    }

    /**
     * Returns a mysql date string.
     *
     * @param string the value to convert
     * @return string
     */
    public function convertToDate($value)
    {
        return date('Y-m-d', strtotime($value));
    }

    /**
     * Returns a mysql time string.
     *
     * @param string the value to convert
     * @return string
     */
    public function convertToTime($value)
    {
        return date('H:i:s', strtotime($value));
    }

        if (! isset($this->map[$this->bean->op])) {
            throw new Exception('Filter operator has no template');
        }

        switch ($this->bean->op) {
            case 'like':
                $value = '%'.str_replace($this->pat, $this->rep, $this->bean->value).'%';
                break;
            case 'notlike':
                $value = '%'.str_replace($this->pat, $this->rep, $this->bean->value).'%';
                break;
            case 'bw':
                $value = str_replace($this->pat, $this->rep, $this->bean->value).'%';
                break;
            case 'ew':
                $value = '%'.str_replace($this->pat, $this->rep, $this->bean->value);
                break;
            case 'in':
                $_sharedSubName = 'shared'.ucfirst(strtolower($this->bean->substitute));
                $ids = array_keys($this->bean->{$_sharedSubName});
                $value = implode(', ', $ids);
                $add_to_filter_values = false;
                break;
            default:
                $value = $this->bean->value;
        }
        if ($add_to_filter_values) {
            $converter = 'convertTo' . ucfirst(strtolower($this->bean->tag));
            $filter->filter_values[] = $this->$converter($value);
        }
        return $value;

        if (isset($this->operators[$this->bean->tag])) {
            return $this->operators[$this->bean->tag];
        }