Welcome Guest, Not a member yet? Register   Sign In
Server config causing CSRF triggers
#11

Quote:- I've hardened my FAMP stack and one or more of those customizations triggers CSRF in CI 3.1.2.
- I need to debug to get down to what exactly is causing the issue.

Put die statements in the core/Security class and log it step by step. You aren't triggering CSRF you're breaking it. I'd guess it's related to cookies because CSRF is pretty simple in how it works.
Reply
#12

(This post was last modified: 09-06-2017, 07:04 AM by objecttothis.)

OK, here is an example of a place where CSRF returns a 403 (when CSRF is disabled I get 200).  I can't figure out what in the code is causing CSRF to not like it.

view form.php
PHP Code:
<?php echo form_open($controller_name '/save/' $person_info->person_id, array('id'=>'customer_form''class'=>'form-horizontal')); ?>
.
.
.
<?php echo form_close(); ?>

<script type="text/javascript">
.
.
.
    var csrf_token = function() {
        return Cookies.get('<?php echo $this->config->item('csrf_cookie_name'); ?>');
    };

    var csrf_form_base = function() {
        return { <?php echo $this->security->get_csrf_token_name(); ?> : function () { return csrf_token();  } };
    };
.
.
.
//validation and submit handling
$(document).ready(function()
{
 $('#customer_form').validate($.extend({
 submitHandler: function(form)
 {
 $(form).ajaxSubmit({
 success: function(response)
 {
 dialog_support.hide();
 table_support.handle_submit('<?php echo site_url($controller_name); ?>', response);
 },
 dataType: 'json'
 });
 },

 rules:
 {
 first_name: "required",
 last_name: "required",
     email:
 {
 remote:
 {
 url: "<?php echo site_url($controller_name '/ajax_check_email')?>",
 type: "post",
 data: $.extend(csrf_form_base(),
 {
 "person_id" : "<?php echo $person_info->person_id?>",
 // email is posted by default
 })
 }
 },
     account_number:
 {
 remote:
 {
 url: "<?php echo site_url($controller_name '/ajax_check_account_number')?>",
 type: "post",
 data: $.extend(csrf_form_base(),
 {
 "person_id" : "<?php echo $person_info->person_id?>"
 // account_number is posted by default
 })
 }
 }
   },

 messages: 
 {
     first_name: "<?php echo $this->lang->line('common_first_name_required'); ?>",
     last_name: "<?php echo $this->lang->line('common_last_name_required'); ?>",
     email: "<?php echo $this->lang->line('customers_email_duplicate'); ?>",
 account_number: "<?php echo $this->lang->line('customers_account_number_duplicate'); ?>"
 }
 }, form_support.error));
});

$("input[name='sales_tax_code_name']").change(function() {
    if( ! $("input[name='sales_tax_code_name']").val() ) {
        $("input[name='sales_tax_code']").val('');
    }
});

var fill_value = function(event, ui) {
    event.preventDefault();
    $("input[name='sales_tax_code']").val(ui.item.value);
    $("input[name='sales_tax_code_name']").val(ui.item.label);
};

$("#sales_tax_code_name").autocomplete({
    source: '<?php echo site_url("taxes/suggest_sales_tax_codes"); ?>',
    minChars: 0,
    delay: 15,
    cacheLength: 1,
    appendTo: '.modal-content',
    select: fill_value,
    focus: fill_value
});

</script> 

controller Customers.php
PHP Code:
/*
 AJAX call to verify if an email address already exists
 */
 
public function ajax_check_email()
 {
 
$exists $this->Customer->check_email_exists(strtolower($this->input->post('email')), $this->input->post('person_id'));

 echo !
$exists 'true' 'false';
 } 

model Customer.php

PHP Code:
/*
 Checks if customer email exists
 */
 
public function check_email_exists($email$customer_id '')
 {
 
// if the email is empty return like it is not existing
 
if(empty($email))
 {
 return 
FALSE;
 }

 
$this->db->from('customers');
 
$this->db->join('people''people.person_id = customers.person_id');
 
$this->db->where('people.email'$email);
 
$this->db->where('customers.deleted'0);

 if(!empty(
$customer_id))
 {
 
$this->db->where('customers.person_id !='$customer_id);
 }

 return (
$this->db->get()->num_rows() == 1);
 } 

config.php
PHP Code:
$config['global_xss_filtering'] = FALSE;

$config['csrf_protection'] = TRUE;
$config['csrf_token_name'] = 'csrf_ospos_v3';
$config['csrf_cookie_name'] = 'csrf_cookie_ospos_v3';
$config['csrf_expire'] = 7200;
$config['csrf_regenerate'] = TRUE;
$config['csrf_exclude_uris'] = array(); 
Reply
#13

If I add 'customers/ajax_check_email' to csrf_exclude_uris or set csrf_protection to FALSE it gives me a 200 response code
Reply
#14

OK, we are getting closer. Due to the fact that my application doesn't exhibit the same 403 errors on another server that tells me that it's likely a server configuration that is not compatible with CodeIgniter's CSRF implementation. So, I replaced my httpd.conf and php.ini with default production versions and with just the bare minimum server configuration. I found that I was no longer getting the 403 errors. Then I put my php.ini file back to the way it was and immediately the 403 errors came back. This means that minimally there is a problem with the php.ini configuration. I am going to one-at-a-time reimplement and test each directive to see what's causing it and will report back, since it's likely to be useful for anyone else with the same configuration.
Reply
#15

OK, I finally found the source of the incompatibility with CodeIgniter's CSRF. In php.ini if
Code:
suhosin.cookie.encrypt = On
is found then it causes CSRF in CI to kick back a 403 on ajax requests and probably others, but I noticed it on the axaj calls. There are two solutions.

Unsafe
Comment out
Code:
suhosin.cookie.encrypt = On
in php.ini

Safe
Create the line
Code:
suhosin.cookie.plainlist = [insert csrf cookie name from config.php]
in php.ini with no quotes around the cookie name. Any other cookies should be separated by a comma.
Reply
#16

(This post was last modified: 09-07-2017, 06:36 AM by spjonez.)

Is cookie_httponly set to false? If security is your primary concern this should be set to true which will break the code you posted. Instead of reading the cookie from JS, return the new token with every AJAX call and store it in a variable for subsequent requests.

csrf_regenerate set to true will also cause 403 issues if you make concurrent AJAX calls.
Reply
#17

(09-07-2017, 06:33 AM)spjonez Wrote: Is cookie_httponly set to false? If security is your primary concern this should be set to true which will break the code you posted. Instead of reading the cookie from JS, return the new token with every AJAX call and store it in a variable for subsequent requests.

csrf_regenerate set to true will also cause 403 issues if you make concurrent AJAX calls.

cookie_httponly is currently set to false. We will later rework the code to allow httponly to be enabled.

csrf_regenerate is set to true and so far the AJAX calls haven't been doing things like giving a 200 on the first and 403 on subsequent.

Like I said in my last post, this is clearly being caused by an incompatibility between suhosin.cookie.encrypt and CI's CSRF implementation. That's not to say that your suggestions can't be the cause of problems, but in my case it's suhosin.
Reply
#18

(This post was last modified: 09-07-2017, 11:26 AM by spjonez.)

(09-07-2017, 06:47 AM)objecttothis Wrote: csrf_regenerate is set to true and so far the AJAX calls haven't been doing things like giving a 200 on the first and 403 on subsequent.

As long as only one ever fires and completes at a given time it will work. If you fire one then fire another before the first completes the second you sent will 403. Just thought I'd give you a heads up in case it happens!
Reply
#19

(09-07-2017, 11:26 AM)spjonez Wrote:
(09-07-2017, 06:47 AM)objecttothis Wrote: csrf_regenerate is set to true and so far the AJAX calls haven't been doing things like giving a 200 on the first and 403 on subsequent.

As long as only one ever fires and completes at a given time it will work. If you fire one then fire another before the first completes the second you sent will 403. Just thought I'd give you a heads up in case it happens!

Thanks. I'll keep an eye out for that.
Reply




Theme © iAndrew 2016 - Forum software by © MyBB