CVE-2022-34134 (PHP, published 2022-06-28T00:15Z)
Description: value: 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 v3 base score: 8.8)
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/bbalet/jorani
Files changed:
application/config/routes.php
application/controllers/Users.php
application/views/users/index.php
Commit message: BF:security on users mgt with CSRF token fix #369


Vulnerable code:
$route['users/delete/(:num)'] = 'users/delete/$1';
$route['users/enable/(:num)'] = 'users/enable/$1';
$route['users/disable/(:num)'] = 'users/disable/$1';
     * Set a user as active (TRUE) or inactive (FALSE)
     * @param int $id User identifier
     * @param bool $active active (TRUE) or inactive (FALSE)
    public function active($id, $active) {
        $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);
    /**
     * 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');
    }

    <tr>
                <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="<?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="btn btn-danger" id="lnkDeleteUser"><?php echo lang('users_index_popup_delete_button_yes');?></a>
    $('#users').dataTable({
    //On showing the confirmation pop-up, add the user id at the end of the delete url action
        var link = "<?php echo base_url();?>users/delete/" + $(this).data('id');
        $("#lnkDeleteUser").attr('href', link);

Fixed code:
$route['users/account']['POST'] = 'users/account';
     * Account management (activate/disable/delete) is done by a 
     * POST request with a CSRF token for an improved security
    public function account() {
        $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'));
            }
        }
    <tr data-id="<?php echo $users_item['id']; ?>">
            <?php if ($users_item['id'] != $this->session->userdata('id')) { ?>
                <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>
                <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 if ($users_item['id'] != $this->session->userdata('id')) { ?>
                <?php } ?>
        <a href="#" class="btn btn-danger" id="action-delete"><?php echo lang('users_index_popup_delete_button_yes');?></a>
<script src="<?php echo base_url();?>assets/js/bootbox.min.js"></script>

    <?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');?>");
        }
    });

    oTable = $('#users').DataTable({
    //On showing the confirmation pop-up, add the user id as an attribute of the delete url link
        $("#action-delete").attr('data-id', $(this).data('id'));
    //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');
        });
    });