2
votes

windows 10, codeigniter 3, jquery 3

I am testing the ajax function and I am loading data into a div by using the $.post function and sending to a controller method that reads a file and sends back the contents (I know there are other ways of doing it but this is a test). The ajax call looks like this

    var btn = 'a[href="click-help"]'
    $(btn).click(function(){

            $.post(
                base_url+'site/cal_help', 
                function(data){
                    $('.cal-help').html(data);
                });
        }
        return false;
    });

You can see that the click button is not a form but a link. This works OK when I have csrf set to false but the console shows the call as forbidden if csrf is enabled. Having researched online, I have tried passing data as follows:

    {
                    '<?php echo $this->security->get_csrf_token_name(); ?>' : '<?php echo $this->security->get_csrf_hash(); ?>'
    }, 

but it doesn't help. How can I fix it without setting csrf to false, which I would rather not do in case I want it for other elements in the site?

4

4 Answers

2
votes

I'm still working away at this and I've discovered a few more interesting things:

Although you can use the Codeigniter Security class like this:

<?php echo $this->security->get_csrf_token_name(); ?>
<?php echo $this->security->get_csrf_hash(); ?>

in a view and these functions work, you are also supposed to be able to use them in a script file eg

var hash = '<?php echo $this->security->get_csrf_hash();?>';
console.log(hash);

That seems to work within script tags in a view but if in a separate script.js file the console just shows the code printed out. Why should that happen?

However I dug into js string methods and discovered that the following works to retrieve the hash:

//get all cookies
var x = document.cookie;
//search for the csrf cookie name
var posname = x.search('csrf_cookie_name');
//slice out hash itself which is always 32 characters long and these numbers work
var hash = x.slice(posname+17,posname+49);
console.log(hash);

Having established a variable equal to the hash, you can add the following data to your ajax call:

{
   'csrf_test_name' : hash
},

I put all that inside the 'click' function so it finds the latest version of the cookie. So I end up with something like this:

var btn = '.btn-cal-help a';
$(btn).click(function(){
    var x = document.cookie;
    var posname = x.search('csrf_cookie_name');
    var hash = x.slice(posname+17,posname+49);
    //console.log(hash);    
        $.post(
            base_url + 'site/cal_help', 
            {
                'csrf_test_name' : hash
            }, 
            function(data){
                $('.cal-help').html(data);
            });
    }
    return false;
});

and now that works for me (on my local server - wamp) even though I have 'csrf_regenerate' set to TRUE as well.

0
votes

I do face the similar problem with a project with CSRF protection enabled in all ajax requests. Then I figure out that it happens because the csrf token Regenerate on every request that will result in forbidden error because token doesn't match. So the solution is turn off CSRF token generation on every request. You can do it by setting to this option in your config.php file.

$config['csrf_regenerate'] = FALSE;

Please note that when I checked laravel's CSRF token, it doesn't change in all requests. So changing csrf regeneration option to false will not cause any major security issues. Please comment below if above method doesn't work for you.

0
votes

Simply as this. Get your token and hash for each new request and attach it to your post data

Place something like this in your model/controller

$reponse = array(
                'csrfName' => $this->security->get_csrf_token_name(),
                'csrfHash' => $this->security->get_csrf_hash()
            );
// Your logic

 return $this->output
                            ->set_content_type('application/json')
                            ->set_status_header(200)
                            ->set_output(json_encode(array("regen" => $reponse, "response" => array("status" => $status, "message" => $message))));

In the javascript part

    var csrfName = '<?php echo $this->security->get_csrf_token_name(); ?>',
                csrfHash = '<?php echo $this->security->get_csrf_hash(); ?>';

$.ajax({
                url: __URL__,
                type: "POST",
                data: $form.serialize() + "&" + csrfName + "=" + csrfHash,
                mimeType: "multipart/form-data",
                contentType: false,
                dataType: 'json',
                cache: false,
                processData: false,

                success: function (data) {

                    csrfName = data.regen.csrfName;
                    csrfHash = data.regen.csrfHash;

                    if (data.response.status) // if STATUS = TRUE
                    {
                        if (data.response.message === "__WHATEVER__") {
                            // and message === "__WHATEVER__"

                        }
                    }
                }
            });
0
votes

setting $config['csrf_protection'] = TRUE; to FALSE is not a good solution to that problem. What happen during the AJAX request is when the request was sent to your controller it will verify for a valid token during the submition. So you need to pass the token name and hashed token so that it will be read as valid.

Here it is...

$.ajax({
       url: url,
       type: "post",
       data: {'<?php echo $this->security->get_csrf_token_name(); ?>':'<?php echo $this->security->get_csrf_hash(); ?>'},
            success:function() {
                              //do stuff here...
                            }
 });