CVE-2020-36622 (published 2022-12-21T19:15Z)

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.

CVSS v3 base score: 6.5, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N)

Weakness information:
cwe_id: CWE-352
cwe_name: Cross-Site Request Forgery (CSRF)
description: 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.
extended_description: When a web server is designed to receive a request from a client without any mechanism for verifying that it was intentionally sent, then it might be possible for an attacker to trick a client into making an unintentional request to the web server which will be treated as an authentic request. This can be done via a URL, image load, XMLHttpRequest, etc. and can result in exposure of data or unintended code execution.
         

Fix information:
repo_url: https://github.com/sah-comp/bienlein
commit_details: repo_url: https://github.com/sah-comp/bienlein
author: sah-comp
author_date: 2020-09-28 17:07:51+02:00
msg: Added CSRF prevention
repository: repo_url: https://github.com/sah-comp/bienlein
repo_name: sah-comp/bienlein
description: Core application using FlightPHP and RedBeanPHP
repo_language: PHP
owner: sah-comp
file_changes: filename: changepassword.php
change_type: ModificationType.MODIFY
diff: @@ -21,7 +21,10 @@
         class="panel panel-<?php echo $record->getMeta('type') ?> action-changepassword"
         method="POST"
         accept-charset="utf-8"
-        enctype="multipart/form-data">        
+        enctype="multipart/form-data">
+
+        <input type="hidden" name="token" value="<?php echo Model::getCSRFToken() ?>" />
+
         <!-- changepassword form -->
         <fieldset>
             <legend><?php echo I18n::__('account_legend_changepassword') ?></legend>

programming_language: PHP
filename: index.php
change_type: ModificationType.MODIFY
diff: @@ -22,6 +22,9 @@
         method="POST"
         accept-charset="utf-8"
         enctype="multipart/form-data">
+
+        <input type="hidden" name="token" value="<?php echo Model::getCSRFToken() ?>" />
+        
         <div>
             <img
         		src="<?php echo Gravatar::src($record->email, 72) ?>"
@@ -29,7 +32,7 @@
         		width="72"
         		height="72"
         		alt="<?php echo htmlspecialchars($record->getName()) ?>" />
-        </div>     
+        </div>
         <!-- account form -->
         <fieldset>
             <legend><?php echo I18n::__('account_legend') ?></legend>

programming_language: PHP
filename: login.php
change_type: ModificationType.MODIFY
diff: @@ -29,6 +29,9 @@
         class="panel login"
         method="POST"
         accept-charset="utf-8">
+
+        <input type="hidden" name="token" value="<?php echo Model::getCSRFToken() ?>" />
+
         <div>
             <input type="hidden" name="dialog[type]" value="<?php echo $record->getMeta('type') ?>" />
             <input type="hidden" name="dialog[id]" value="<?php echo $record->getId() ?>" />

programming_language: PHP
filename: lostpassword.php
change_type: ModificationType.MODIFY
diff: @@ -29,6 +29,9 @@
         class="panel lostpassword"
         method="POST"
         accept-charset="utf-8">
+
+        <input type="hidden" name="token" value="<?php echo Model::getCSRFToken() ?>" />
+
         <fieldset>
             <legend><?php echo I18n::__('lostpassword_legend') ?></legend>
             <div

programming_language: PHP
filename: index.php
change_type: ModificationType.MODIFY
diff: @@ -19,6 +19,9 @@
         method="POST"
         accept-charset="utf-8"
         enctype="multipart/form-data">
+
+        <input type="hidden" name="token" value="<?php echo Model::getCSRFToken() ?>" />
+
         <?php echo $form_details ?>
         <div class="buttons">
             <input

programming_language: PHP
filename: form.php
change_type: ModificationType.MODIFY
diff: @@ -22,11 +22,13 @@
         method="POST"
         accept-charset="utf-8"
         enctype="multipart/form-data">
-        
+
+        <input type="hidden" name="token" value="<?php echo Model::getCSRFToken() ?>" />
+
         <!-- form details -->
         <?php echo $form_details ?>
         <!-- end of form details -->
-        
+
         <!-- Scaffold buttons -->
         <div class="buttons">
             <select name="next_action">

programming_language: PHP
filename: Account.php
change_type: ModificationType.MODIFY
diff: @@ -35,22 +35,24 @@ class Controller_Account extends Controller
         session_start();
         Auth::check();
         $this->template = 'account/index';
-        
+
         if (Flight::request()->method == 'POST') {
+            if (! Model::validateCSRFToken(Flight::request()->data->token)) {
+                $this->redirect("/logout");
+            }
             Flight::get('user')->import(Flight::request()->data->dialog);
             try {
                 R::store(Flight::get('user'));
                 Flight::get('user')->notify(I18n::__('account_edit_success'), 'success');
                 $this->redirect('/account/');
-            }
-            catch (Exception $e) {
+            } catch (Exception $e) {
                 Flight::get('user')->notify(I18n::__('account_edit_failure'), 'error');
-            }    
+            }
         }
-        
+
         $this->render();
     }
-    
+
     /**
      * Displays a page to change the password.
      *
@@ -62,8 +64,11 @@ class Controller_Account extends Controller
         session_start();
         Auth::check();
         $this->template = 'account/changepassword';
-        
+
         if (Flight::request()->method == 'POST') {
+            if (! Model::validateCSRFToken(Flight::request()->data->token)) {
+                $this->redirect("/logout");
+            }
             if (Flight::get('user')->changePassword(
                 Flight::request()->data->pw,
                 Flight::request()->data->pw_new,
@@ -73,35 +78,33 @@ class Controller_Account extends Controller
                     R::store(Flight::get('user'));
                     Flight::get('user')->notify(I18n::__('account_changepassword_success'), 'success');
                     $this->redirect('/account/');
-                }
-                catch (Exception $e) {
+                } catch (Exception $e) {
                     //Whoops, what nu?
                 }
-            }
-            else {
+            } else {
                 Flight::get('user')->notify(I18n::__('account_changepassword_failure'), 'error');
-            }    
+            }
         }
-        
+
         $this->render();
     }
-    
+
     /**
      * Renders the account page.
      */
     protected function render()
     {
-	    Flight::render('shared/notification', array(), 'notification');
-	    //
+        Flight::render('shared/notification', array(), 'notification');
+        //
         Flight::render('shared/navigation/account', array(), 'navigation_account');
-		Flight::render('shared/navigation/main', array(), 'navigation_main');
+        Flight::render('shared/navigation/main', array(), 'navigation_main');
         Flight::render('shared/navigation', array(), 'navigation');
         Flight::render('account/toolbar', array(), 'toolbar');
-		Flight::render('shared/header', array(), 'header');
-		Flight::render('shared/footer', array(), 'footer');
-		Flight::render($this->template, array(
-		    'record' => Flight::get('user')
-		), 'content');
+        Flight::render('shared/header', array(), 'header');
+        Flight::render('shared/footer', array(), 'footer');
+        Flight::render($this->template, array(
+            'record' => Flight::get('user')
+        ), 'content');
         Flight::render('html5', array(
             'title' => I18n::__("account_head_title"),
             'language' => Flight::get('language')

programming_language: PHP
filename: Login.php
change_type: ModificationType.MODIFY
diff: @@ -33,13 +33,16 @@ class Controller_Login extends Controller
     public function index()
     {
         session_start();
-        if ( ! isset($_SESSION['login_id'])) {
+        if (! isset($_SESSION['login_id'])) {
             $_SESSION['login_id'] = 0;
         }
         $login = R::load('login', $_SESSION['login_id']);
         if (Flight::request()->method == 'POST') {
+            if (! Model::validateCSRFToken(Flight::request()->data->token)) {
+                $this->redirect("/logout");
+            }
             try {
-                $login = R::graph( Flight::request()->data->dialog, TRUE );
+                $login = R::graph(Flight::request()->data->dialog, true);
                 if ($login->trial()) {
                     //you must trial before store because of pw reset in update
                     $_SESSION['user']['id'] = $login->user->getId();
@@ -56,7 +59,7 @@ class Controller_Login extends Controller
             }
         }
         // either no yet submitted or the credentials given failed
-        if ( Flight::request()->query->goto == '' || Flight::request()->query->goto == '/login' ) {
+        if (Flight::request()->query->goto == '' || Flight::request()->query->goto == '/login') {
             $goto = '/cms';
         } else {
             $goto = Flight::request()->query->goto;

programming_language: PHP
filename: Lostpassword.php
change_type: ModificationType.MODIFY
diff: @@ -23,12 +23,12 @@ class Controller_Lostpassword extends Controller
      * @var string
      */
     public $template = 'account/lostpassword';
-    
+
     /**
      * Holds the username entered by the password seeking party.
      */
     public $uname = '';
-    
+
     /**
      * May hold a message (textile) to the user that want to re-enter a password.
      */
@@ -43,28 +43,31 @@ class Controller_Lostpassword extends Controller
     public function index()
     {
         if (Flight::request()->method == 'POST') {
+            if (! Model::validateCSRFToken(Flight::request()->data->token)) {
+                $this->redirect("/logout");
+            }
             $this->uname = Flight::request()->data->dialog['uname'];
-            if ( ! $user = R::findOne('user', ' email = ? LIMIT 1 ', array($this->uname))) {
+            if (! $user = R::findOne('user', ' email = ? LIMIT 1 ', array($this->uname))) {
                 $this->message = I18n::__('lostpassword_user_unknown');
-            } elseif ( ! $user->requestPassword()) {
+            } elseif (! $user->requestPassword()) {
                 $this->message = I18n::__('lostpassword_email_failed');
             } else {
                 $this->redirect('login');
             }
         }
-        
+
         $this->render();
     }
-    
+
     /**
      * Renders the lostpassword page.
      */
     protected function render()
     {
-		Flight::render($this->template, array(
-		    'uname' => $this->uname,
-		    'message' => $this->message
-		), 'content');
+        Flight::render($this->template, array(
+            'uname' => $this->uname,
+            'message' => $this->message
+        ), 'content');
         Flight::render('html5', array(
             'title' => I18n::__("lostpassword_head_title"),
             'language' => Flight::get('language'),

programming_language: PHP
filename: Scaffold.php
change_type: ModificationType.MODIFY
diff: @@ -488,6 +488,9 @@ class Controller_Scaffold extends Controller
         //$this->template = "model/{$this->type}/{$this->layout}";
         $this->template = "scaffold/{$this->layout}";
         if (Flight::request()->method == 'POST') {
+            if (! Model::validateCSRFToken(Flight::request()->data->token)) {
+                $this->redirect("/logout");
+            }
             //clear filter?
             if (Flight::request()->data->submit == I18n::__('filter_submit_clear')) {
                 R::trash($this->filter);
@@ -559,6 +562,9 @@ class Controller_Scaffold extends Controller
             }
         }
         if (Flight::request()->method == 'POST') {
+            if (! Model::validateCSRFToken(Flight::request()->data->token)) {
+                $this->redirect("/logout");
+            }
             $this->record = R::graph(Flight::request()->data->dialog, true);
             $this->setNextAction(Flight::request()->data->next_action);
             if ($this->doRedbeanAction()) {
@@ -605,6 +611,9 @@ class Controller_Scaffold extends Controller
             $this->template = "scaffold/edit";
         }
         if (Flight::request()->method == 'POST') {
+            if (! Model::validateCSRFToken(Flight::request()->data->token)) {
+                $this->redirect("/logout");
+            }
             Permission::check(Flight::get('user'), $this->type, 'edit');//check for edit perm now
             $this->record = R::graph(Flight::request()->data->dialog, true);
             $this->setNextAction(Flight::request()->data->next_action);

programming_language: PHP
filename: Model.php
change_type: ModificationType.MODIFY
diff: @@ -87,6 +87,39 @@ class Model extends RedBean_SimpleModel
     {
     }
 
+    /**
+     * 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;
+    }
+
     /**
      * Returns an array with attributes for lists.
      *

programming_language: PHP
filename: Criteria.php
change_type: ModificationType.MODIFY
diff: @@ -23,19 +23,19 @@ class Model_Criteria extends Model
      * @var array
      */
     public $map = array(
- 		'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')
- 	);
+        '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')
+    );
 
     /**
      * Holds possible search operators depending on the filter tag type.
@@ -58,120 +58,131 @@ class Model_Criteria extends Model
         'bool' => array('eq')
      );
 
-     /**
-      * 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));
-     }
-    
+    /**
+     * 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));
+    }
+
     /**
      * Returns a string to use as part of a SQL query.
      *
@@ -183,12 +194,14 @@ class Model_Criteria extends Model
      */
     public function makeWherePart(Model_Filter $filter)
     {
-        if ( ! isset($this->map[$this->bean->op])) throw new Exception('Filter operator has no template');
+        if (! isset($this->map[$this->bean->op])) {
+            throw new Exception('Filter operator has no template');
+        }
         $template = $this->map[$this->bean->op];
         $value = $this->mask_filter_value($filter);
         return sprintf($template, $this->bean->attribute, $value);
     }
-    
+
     /**
      * Masks the criterias value and stacks it into the filter values.
      *
@@ -199,35 +212,35 @@ class Model_Criteria extends Model
     protected function mask_filter_value(Model_Filter $filter)
     {
         $add_to_filter_values = true;
-    	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;
+        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;
     }
-    
+
     /**
      * Returns array with possible operators for the given tag type.
      *
@@ -235,10 +248,12 @@ class Model_Criteria extends Model
      */
     public function operators()
     {
-        if (isset($this->operators[$this->bean->tag])) return $this->operators[$this->bean->tag];
+        if (isset($this->operators[$this->bean->tag])) {
+            return $this->operators[$this->bean->tag];
+        }
         return array();
     }
-    
+
     /**
      * Setup validators.
      */

programming_language: PHP