CVE-2022-34134 (published 2022-06-28T00:15Z)

Benjamin BALET Jorani v1.0 was discovered to contain a Cross-Site Request Forgery (CSRF) via the component /application/controllers/Users.php.

Severity: MEDIUM
CVSS v2 base score: 6.8 (AV:N/AC:M/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 8.8, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H)

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/bbalet/jorani
commit_details: repo_url: https://github.com/bbalet/jorani
author: Benjamin BALET
author_date: 2022-06-06 17:29:03+02:00
msg: BF:security on users mgt with CSRF token fix #369
repository: repo_url: https://github.com/bbalet/jorani
repo_name: bbalet/jorani
description: Leave and Overtime Management System
repo_language: PHP
owner: bbalet
file_changes: filename: routes.php
change_type: ModificationType.MODIFY
diff: @@ -58,10 +58,8 @@ $route['users/export'] = 'users/export';
 $route['users/reset/(:num)'] = 'users/reset/$1';
 $route['users/create'] = 'users/create';
 $route['users/edit/(:num)'] = 'users/edit/$1';
-$route['users/delete/(:num)'] = 'users/delete/$1';
 $route['users/check/login'] = 'users/checkLoginByAjax';
-$route['users/enable/(:num)'] = 'users/enable/$1';
-$route['users/disable/(:num)'] = 'users/disable/$1';
+$route['users/account']['POST'] = 'users/account';
 $route['users'] = 'users';
 
 //_______________________________________________

programming_language: PHP
filename: Users.php
change_type: ModificationType.MODIFY
diff: @@ -46,34 +46,34 @@ class Users extends CI_Controller {
     }
 
     /**
-     * Set a user as active (TRUE) or inactive (FALSE)
-     * @param int $id User identifier
-     * @param bool $active active (TRUE) or inactive (FALSE)
+     * Account management (activate/disable/delete) is done by a 
+     * POST request with a CSRF token for an improved security
      * @author Benjamin BALET <benjamin.balet@gmail.com>
      */
-    public function active($id, $active) {
+    public function account() {
         $this->auth->checkIfOperationIsAllowed('list_users');
-        $this->users_model->setActive($id, $active);
-        $this->session->set_flashdata('msg', lang('users_edit_flash_msg_success'));
-        redirect('users');
-    }
-
-    /**
-     * Enable a user
-     * @param int $id User identifier
-     * @author Benjamin BALET <benjamin.balet@gmail.com>
-     */
-    public function enable($id) {
-        $this->active($id, TRUE);
-    }
-
-    /**
-     * Disable a user
-     * @param int $id User identifier
-     * @author Benjamin BALET <benjamin.balet@gmail.com>
-     */
-    public function disable($id) {
-        $this->active($id, FALSE);
+        $id = $this->input->post('id');
+        $operation = $this->input->post('operation');
+        //Test if user exists
+        $data['users_item'] = $this->users_model->getUsers($id);
+        if (empty($data['users_item'])) {
+            redirect('notfound');
+        } else {
+            switch ($operation) {
+                case 'enable': 
+                    $this->users_model->setActive($id, TRUE);
+                    log_message('error', 'User #' . $id . ' has been enabled by user #' . $this->session->userdata('id'));
+                    break;
+                case 'disable':
+                    $this->users_model->setActive($id, FALSE);
+                    log_message('error', 'User #' . $id . ' has been disabled by user #' . $this->session->userdata('id'));
+                    break;
+                case 'delete':
+                    $this->auth->checkIfOperationIsAllowed('delete_user');
+                    $this->users_model->deleteUser($id);
+                    log_message('error', 'User #' . $id . ' has been deleted by user #' . $this->session->userdata('id'));
+            }
+        }
     }
 
     /**
@@ -194,25 +194,6 @@ class Users extends CI_Controller {
         }
     }
 
-    /**
-     * Delete a user. Log it as an error.
-     * @param int $id User identifier
-     * @author Benjamin BALET <benjamin.balet@gmail.com>
-     */
-    public function delete($id) {
-        $this->auth->checkIfOperationIsAllowed('delete_user');
-        //Test if user exists
-        $data['users_item'] = $this->users_model->getUsers($id);
-        if (empty($data['users_item'])) {
-            redirect('notfound');
-        } else {
-            $this->users_model->deleteUser($id);
-        }
-        log_message('error', 'User #' . $id . ' has been deleted by user #' . $this->session->userdata('id'));
-        $this->session->set_flashdata('msg', lang('users_delete_flash_msg_success'));
-        redirect('users');
-    }
-
     /**
      * Reset the password of a user
      * Can be accessed by the user itself or by admin

programming_language: PHP
filename: index.php
change_type: ModificationType.MODIFY
diff: @@ -28,20 +28,24 @@
     </thead>
     <tbody>
 <?php foreach ($users as $users_item): ?>
-    <tr>
+    <tr data-id="<?php echo $users_item['id']; ?>">
         <td data-order="<?php echo $users_item['id']; ?>">
             <?php echo $users_item['id'] ?>&nbsp;
             <div class="pull-right">
+            <?php if ($users_item['id'] != $this->session->userdata('id')) { ?>
                 <?php if ($users_item['active']) { ?>
-                <a href="<?php echo base_url();?>users/disable/<?php echo $users_item['id'] ?>" title="<?php echo lang('users_index_thead_tip_active');?>"><i class="mdi mdi-account-off nolink"></i></a>
+                <a href="#" class="action-disable" data-id="<?php echo $users_item['id'];?>" title="<?php echo lang('users_index_thead_tip_active');?>"><i class="mdi mdi-account-off nolink"></i></a>
                 <?php } else { ?>
-                <a href="<?php echo base_url();?>users/enable/<?php echo $users_item['id'] ?>" title="<?php echo lang('users_index_thead_tip_inactive');?>"><i class="mdi mdi-account mdi-dark mdi-inactive"></i></a>
+                <a href="#" class="action-enable" data-id="<?php echo $users_item['id'];?>" title="<?php echo lang('users_index_thead_tip_inactive');?>"><i class="mdi mdi-account mdi-dark mdi-inactive"></i></a>
                 <?php } ?>
+            <?php } ?>
                 &nbsp;
                 <a href="<?php echo base_url();?>users/edit/<?php echo $users_item['id'] ?>" title="<?php echo lang('users_index_thead_tip_edit');?>"><i class="mdi mdi-account-edit nolink"></i></a>
                 &nbsp;
+                <?php if ($users_item['id'] != $this->session->userdata('id')) { ?>
                 <a href="#" class="confirm-delete" data-id="<?php echo $users_item['id'];?>" title="<?php echo lang('users_index_thead_tip_delete');?>"><i class="mdi mdi-delete nolink"></i></a>
                 &nbsp;
+                <?php } ?>
                 <a href="<?php echo base_url();?>users/reset/<?php echo $users_item['id'] ?>" title="<?php echo lang('users_index_thead_tip_reset');?>" data-target="#frmResetPwd" data-toggle="modal"><i class="mdi mdi-lock nolink"></i></a>
             </div>
         </td>
@@ -79,7 +83,7 @@
         <p><?php echo lang('users_index_popup_delete_question');?></p>
     </div>
     <div class="modal-footer">
-        <a href="#" class="btn btn-danger" id="lnkDeleteUser"><?php echo lang('users_index_popup_delete_button_yes');?></a>
+        <a href="#" class="btn btn-danger" id="action-delete"><?php echo lang('users_index_popup_delete_button_yes');?></a>
         <a href="#" onclick="$('#frmConfirmDelete').modal('hide');" class="btn"><?php echo lang('users_index_popup_delete_button_no');?></a>
     </div>
 </div>
@@ -119,11 +123,34 @@
 
 <link href="<?php echo base_url();?>assets/datatable/DataTables-1.10.11/css/jquery.dataTables.min.css" rel="stylesheet">
 <script type="text/javascript" src="<?php echo base_url();?>assets/datatable/DataTables-1.10.11/js/jquery.dataTables.min.js"></script>
+<script src="<?php echo base_url();?>assets/js/bootbox.min.js"></script>
 
 <script type="text/javascript">
 $(document).ready(function() {
+
+    <?php if ($this->config->item('csrf_protection') == TRUE) {?>
+    $.ajaxSetup({
+        data: {
+            <?php echo $this->security->get_csrf_token_name();?>: "<?php echo $this->security->get_csrf_hash();?>",
+        }
+    });
+    <?php }?>
+
+    //Global Ajax error handling mainly used for session expiration
+    $( document ).ajaxError(function(event, jqXHR, settings, errorThrown) {
+        $('#frmModalAjaxWait').modal('hide');
+        if (jqXHR.status == 401) {
+            bootbox.alert("<?php echo lang('global_ajax_timeout');?>", function() {
+                //After the login page, we'll be redirected to the current page
+                location.reload();
+            });
+        } else { //Oups
+            bootbox.alert("<?php echo lang('global_ajax_error');?>");
+        }
+    });
+
     //Transform the HTML table in a fancy datatable
-    $('#users').dataTable({
+    oTable = $('#users').DataTable({
         stateSave: true,
         language: {
             decimal:            "<?php echo lang('datatable_sInfoThousands');?>",
@@ -152,10 +179,9 @@ $(document).ready(function() {
     $("#frmResetPwd").alert();
     $("#frmImportUsers").alert();
 
-    //On showing the confirmation pop-up, add the user id at the end of the delete url action
+    //On showing the confirmation pop-up, add the user id as an attribute of the delete url link
     $('#frmConfirmDelete').on('show', function() {
-        var link = "<?php echo base_url();?>users/delete/" + $(this).data('id');
-        $("#lnkDeleteUser").attr('href', link);
+        $("#action-delete").attr('data-id', $(this).data('id'));
     });
 
     //Display a modal pop-up so as to confirm if a user has to be deleted or not
@@ -166,6 +192,37 @@ $(document).ready(function() {
         $('#frmConfirmDelete').data('id', id).modal('show');
     });
 
+    //Enable a user
+    $("#users tbody").on('click', '.action-enable',  function(){
+        var id = $(this).data('id');
+        var ref = $(this);
+        $.post( "<?php echo base_url();?>users/account", { operation: "enable", id: id }).done(function() {
+            ref.attr('class', 'action-disable');
+            ref.attr('title', '<?php echo lang('users_index_thead_tip_active');?>');
+            ref.children(":first").attr('class', 'mdi mdi-account-off nolink');
+        });
+    });
+
+    //Disable a user
+    $("#users tbody").on('click', '.action-disable',  function(){
+        var id = $(this).data('id');
+        var ref = $(this);
+        $.post( "<?php echo base_url();?>users/account", { operation: "disable", id: id }).done(function() {
+            ref.attr('class', 'action-enable');
+            ref.attr('title', '<?php echo lang('users_index_thead_tip_inactive');?>');
+            ref.children(":first").attr('class', 'mdi mdi-account mdi-dark mdi-inactive');
+        });
+    });
+
+    //Delete a user
+    $("#action-delete").on('click',  function(){
+        var id = $(this).data('id');
+        $.post( "<?php echo base_url();?>users/account", { operation: "delete", id: id }).done(function() {
+            oTable.rows('tr[data-id="' + id + '"]').remove().draw();
+            $('#frmConfirmDelete').modal('hide');
+        });
+    });
+
     //Prevent to load always the same content (refreshed each time)
     $('#frmConfirmDelete').on('hidden', function() {
         $(this).removeData('modal');

programming_language: PHP