Welcome Guest, Not a member yet? Register   Sign In
CSRF and double posting
#1

(This post was last modified: 06-18-2016, 11:09 PM by PaulD. Edit Reason: Added clarification )

Hi,

I have the latest CI and CSRF is enabled and working. But I noticed that if I double click really, really quickly, on the submit button, I can still post twice. In this case I am submitting to a controller that, upon success, redirects and refreshes to another controller. I thought, because the CSRF was regenerated, the second submit would not work.

To prevent this, because it is annoying me now, do I have to resort to some javascript to prevent the double click?

Just in case I am doing something wrong (although I am pretty sure I am not), here is my CSRF config:
PHP Code:
$config['csrf_protection'] = TRUE;
$config['csrf_token_name'] = 'csrfbpbtok';
$config['csrf_cookie_name'] = 'csrfbpb';
$config['csrf_expire'] = 7200;
$config['csrf_regenerate'] = TRUE;
$config['csrf_exclude_uris'] = array(); 

And the form is outputting the CSRF code:
Code:
<form action="...my url..." method="post" accept-charset="utf-8">
<input type="hidden" name="csrfbpbtok" value="19ac6f0516d5e63e665f3a59bf350f99" style="display:none;" />

Am I just tired and doing something daft? Or is the very quick double click a separate issue entirely? After all, the CSRF stops other people faking posts, not double clicks. But shouldn't the regenerated token prevent it?

Thanks in advance,

Paul.
Reply
#2

It's been a while since I've looked at it, but it was my understanding that the CSRF token is not always regenerated, and one such case would be during an AJAX request ( or at least that was the way it used to work ). I never liked the way CI handles the CSRF token, so I created my own Tokens library. My first version of that library ensured a new token on each request, but then I found that to be problematic, because a person could not have two browser tabs open without running into problems. So the latest version uses an array of tokens, and only subtracts one when it is used. This has been working great for me on my websites.

I do think that the double submission issue is directly related to form tokens. CI chooses to call it a "CSRF token", but it is a common use to use them to ensure that duplicate submissions are not allowed.

Lastly, in my own forms, if AJAX requests are being made, I often will disable the form buttons until I get a response, but also show a network activity indicator (image) so that people don't think that nothing is happening.
Reply
#3

(This post was last modified: 06-19-2016, 12:49 AM by PaulD. Edit Reason: added some PS's )

Hi,

Thanks for that answer. I agree that Ajax is a different issue and I have just been trying to exclude some ajax URI's but the regex's defeated me (it is a very dynamic url), so instead reverted to the more usual including of the hash and token directly in the js. And as far as I have experienced it, ajax calls regenerate the CSRF too.

In the example I am looking at though, it is a straight up HTML form. In fact, all it does is add a new record so the form is just taking a name/title for the record, and that is it. But double clicking the button quickly is creating two records to be inserted. Which is quite frustrating. This example is my own site that I am just building for myself, but this relatively small issue has really irked me and the more I have tried to fix it the more it is annoying me.

I am just having a look at the CI token handling now, as I cannot believe that a double post has time to pass two checks before CI updates the token, but if it happens at different stages of the process, and not immediately upon the first check, then I suppose it might.

The file you linked to is coded so beautifully, it was a pleasure to have a read of it. Thank you. I recently chose ion_auth over community_auth but it was a hard and probably personal decision. Look forward to trying community auth though.

Best wishes,

Paul.

PS I too never leave a page hanging and love using those tiny animated loading gifs for ajax messaging during ajax calls. They childishly always make me feel all 'professional' even though they are just tiny gifs :-)
PPS I love making them as well - customers are always so impressed with a little personalized favicon based loading gif :-)
Reply
#4

Sorry to double post.

It seems that the reset is done immediately after a check, so it cannot be because of that. So why does the second post after a double click succeed?

PHP Code:
security.php ~L226...
        
// Do the tokens exist in both the _POST and _COOKIE arrays?
        
if ( ! isset($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name])
            OR 
$_POST[$this->_csrf_token_name] !== $_COOKIE[$this->_csrf_cookie_name]) // Do the tokens match?
        
{
            
$this->csrf_show_error();
        }

        
// We kill this since we're done and we don't want to polute the _POST array
        
unset($_POST[$this->_csrf_token_name]);

        
// Regenerate on every submission?
        
if (config_item('csrf_regenerate'))
        {
            
// Nothing should last forever
            
unset($_COOKIE[$this->_csrf_cookie_name]);
            
$this->_csrf_hash NULL;
        } 

I didn't think it would be a CI issue. I must be doing something daft.

Maybe I should come back to this later Sad
Reply
#5

(This post was last modified: 06-20-2016, 04:13 PM by PaulD. Edit Reason: Added PS / Added PPS )

Ok, so I reverted to some jquery but it seems daft.  Dodgy

Code:
<script>
  $('input[type=submit]').click(function(event){
        event.preventDefault;
        $('input[type=submit]').attr('disabled','disabled');
        $('form#addForm').submit();
  });
</script>

Before I did that though I turned on information logging, and this is what the log had in it: (Have highlighted two lines in particular)

Quote:INFO - 2016-06-19 10:25:53 --> Config Class Initialized
INFO - 2016-06-19 10:25:53 --> Hooks Class Initialized
INFO - 2016-06-19 10:25:53 --> Utf8 Class Initialized
INFO - 2016-06-19 10:25:53 --> URI Class Initialized
INFO - 2016-06-19 10:25:53 --> Router Class Initialized
INFO - 2016-06-19 10:25:53 --> Output Class Initialized
INFO - 2016-06-19 10:25:53 --> Security Class Initialized
INFO - 2016-06-19 10:25:53 --> CSRF cookie sent
INFO - 2016-06-19 10:25:53 --> CSRF token verified
INFO - 2016-06-19 10:25:53 --> Input Class Initialized
INFO - 2016-06-19 10:25:53 --> Language Class Initialized
INFO - 2016-06-19 10:25:53 --> Loader Class Initialized
INFO - 2016-06-19 10:25:53 --> Helper loaded: url_helper
INFO - 2016-06-19 10:25:53 --> Helper loaded: form_helper
INFO - 2016-06-19 10:25:53 --> Helper loaded: security_helper
INFO - 2016-06-19 10:25:53 --> Database Driver Class Initialized
INFO - 2016-06-19 10:25:53 --> Session: Class initialized using 'database' driver.
INFO - 2016-06-19 10:25:53 --> Form Validation Class Initialized
INFO - 2016-06-19 10:25:53 --> Email Class Initialized
INFO - 2016-06-19 10:25:53 --> Language file loaded: language/english/ion_auth_lang.php
INFO - 2016-06-19 10:25:53 --> Helper loaded: cookie_helper
INFO - 2016-06-19 10:25:53 --> Helper loaded: language_helper
INFO - 2016-06-19 10:25:53 --> Model Class Initialized
INFO - 2016-06-19 10:25:53 --> Helper loaded: date_helper
INFO - 2016-06-19 10:25:53 --> Model Class Initialized
INFO - 2016-06-19 10:25:53 --> Controller Class Initialized
INFO - 2016-06-19 10:25:53 --> Language file loaded: language/english/form_validation_lang.php
INFO - 2016-06-19 10:25:53 --> Config Class Initialized
INFO - 2016-06-19 10:25:53 --> Hooks Class Initialized
INFO - 2016-06-19 10:25:53 --> Utf8 Class Initialized
INFO - 2016-06-19 10:25:53 --> URI Class Initialized
INFO - 2016-06-19 10:25:53 --> Router Class Initialized
INFO - 2016-06-19 10:25:53 --> Output Class Initialized
INFO - 2016-06-19 10:25:53 --> Security Class Initialized
INFO - 2016-06-19 10:25:53 --> CSRF cookie sent
INFO - 2016-06-19 10:25:53 --> CSRF token verified
INFO - 2016-06-19 10:25:53 --> Input Class Initialized
INFO - 2016-06-19 10:25:53 --> Language Class Initialized
INFO - 2016-06-19 10:25:53 --> Loader Class Initialized
INFO - 2016-06-19 10:25:53 --> Helper loaded: url_helper
INFO - 2016-06-19 10:25:53 --> Helper loaded: form_helper
INFO - 2016-06-19 10:25:53 --> Helper loaded: security_helper
INFO - 2016-06-19 10:25:53 --> Database Driver Class Initialized
INFO - 2016-06-19 10:25:53 --> Session: Class initialized using 'database' driver.
INFO - 2016-06-19 10:25:53 --> Form Validation Class Initialized
INFO - 2016-06-19 10:25:53 --> Email Class Initialized
INFO - 2016-06-19 10:25:53 --> Language file loaded: language/english/ion_auth_lang.php
INFO - 2016-06-19 10:25:53 --> Helper loaded: cookie_helper
INFO - 2016-06-19 10:25:53 --> Helper loaded: language_helper
INFO - 2016-06-19 10:25:53 --> Model Class Initialized
INFO - 2016-06-19 10:25:53 --> Helper loaded: date_helper
INFO - 2016-06-19 10:25:53 --> Model Class Initialized
INFO - 2016-06-19 10:25:53 --> Controller Class Initialized
INFO - 2016-06-19 10:25:53 --> Language file loaded: language/english/form_validation_lang.php
INFO - 2016-06-19 10:25:53 --> Config Class Initialized
INFO - 2016-06-19 10:25:53 --> Hooks Class Initialized
INFO - 2016-06-19 10:25:53 --> Utf8 Class Initialized
INFO - 2016-06-19 10:25:53 --> URI Class Initialized
INFO - 2016-06-19 10:25:53 --> Router Class Initialized
INFO - 2016-06-19 10:25:53 --> Output Class Initialized
INFO - 2016-06-19 10:25:53 --> Security Class Initialized
INFO - 2016-06-19 10:25:53 --> CSRF cookie sent
INFO - 2016-06-19 10:25:53 --> Input Class Initialized
INFO - 2016-06-19 10:25:53 --> Language Class Initialized
INFO - 2016-06-19 10:25:53 --> Loader Class Initialized
INFO - 2016-06-19 10:25:53 --> Helper loaded: url_helper
INFO - 2016-06-19 10:25:53 --> Helper loaded: form_helper
INFO - 2016-06-19 10:25:53 --> Helper loaded: security_helper
INFO - 2016-06-19 10:25:53 --> Database Driver Class Initialized
INFO - 2016-06-19 10:25:53 --> Session: Class initialized using 'database' driver.
INFO - 2016-06-19 10:25:53 --> Form Validation Class Initialized
INFO - 2016-06-19 10:25:53 --> Email Class Initialized
INFO - 2016-06-19 10:25:53 --> Language file loaded: language/english/ion_auth_lang.php
INFO - 2016-06-19 10:25:53 --> Helper loaded: cookie_helper
INFO - 2016-06-19 10:25:53 --> Helper loaded: language_helper
INFO - 2016-06-19 10:25:53 --> Model Class Initialized
INFO - 2016-06-19 10:25:53 --> Helper loaded: date_helper
INFO - 2016-06-19 10:25:53 --> Model Class Initialized
INFO - 2016-06-19 10:25:53 --> Controller Class Initialized
INFO - 2016-06-19 10:25:53 --> File loaded: /home/sites/xxx.co.uk/public_html/application/views/common/private/header_view.php
INFO - 2016-06-19 10:25:53 --> File loaded: /home/sites/xxx.co.uk/public_html/application/views/common/private/page_title_view.php
INFO - 2016-06-19 10:25:53 --> File loaded: /home/sites/xxx.co.uk/public_html/application/views/common/book/contents_menu_view.php
INFO - 2016-06-19 10:25:53 --> File loaded: /home/sites/xxx.co.uk/public_html/application/views/pages/contents_view.php
INFO - 2016-06-19 10:25:53 --> File loaded: /home/sites/xxx.co.uk/public_html/application/views/common/book/page_close_view.php
INFO - 2016-06-19 10:25:53 --> File loaded: /home/sites/xxx.co.uk/public_html/application/views/common/private/footer_view.php
INFO - 2016-06-19 10:25:53 --> Final output sent to browser

As you can see, it does seem to be running twice. (This log I got by going to the page with the form in question, emptied the log file, did a quick double click, two records were created erroneously, and this was the log. I have highlighted where the token seems to be being verified twice.

I did not know what to do about this so used jquery, as I said earlier, but it feels a bit hacky. However, since this is a user side issue, I suppose it is ok. But why the regenerated token does not kick in I don't know. I tried resetting validation on the form after passing but that didn't help either.

So solved, but still not happy with just the JS solution. I would really like to know why the double post happened and the session seems to think it was one action, not two.

If anyone could shed any light, would be very pleased. In the meantime, and not immediately as I am not overly happy, am going to have to go through adding this silly jquery snippet to every form (and there are loads of them of course - virtually every page).

Paul.
Sad

PS I suddenly thought it might be the browser. So I went to IE instead of chrome and this fast double click/double post does not happen on IE. Perhaps this is just a chrome issue.

PPS Just installed Firefox and it does not happen in Firefox either - what is going on???
BTW The latest version of firefox is soooooo fast - I am really impressed not having used it for some time. Much quicker than chrome. I might start using it as my default. :-)
Reply
#6

See in core/Security.php:


PHP Code:
// Regenerate on every submission?
if (config_item('csrf_regenerate'))
{
       // Nothing should last forever
       unset($_COOKIE[$this->_csrf_cookie_name]);
       $this->_csrf_hash NULL;


If the browser has not received the response from the first request, then it still has the cookie, and it still is posting the token, which means that there will be a match on the server. Normally you hear of race conditions with AJAX, but perhaps here is another race condition? Maybe somebody smart will chime in.

If you think about the way cookies work, their values are stored in the browser, and the server is sort of relying on the browser to supply the cookie contents (a good reason to encrypt cookies). It may be better if the CSRF value was in a session, because then you could store the value on the server instead of the browser. Think about a true native session where the sessions are stored in files or in the database. If the token was used up, there'd be no way to use it twice, right?

My Tokens library would also be effected by this behavior, because it too relies on plain cookies. I think it would be interesting to extend the Security library and convert CI's CSRF protection to use a value stored in a database or native session, just to see if this was truly fixed for you.

Why this only happens in Chrome for you I don't know. It's hard to imagine that Chrome is so slow, but I'm a FF user myself.
Reply
#7

(This post was last modified: 06-19-2016, 03:29 PM by PaulD. Edit Reason: Added PS )

Hmm. Not sure. I think I am confusing myself.

I don't think it is a race condition. Unsetting the global $_COOKIE is a server function and immediate. The Hash is reset and then the new info is sent back to the existing browsers cookie in the header info on next page load. Where I get confused though is that two posts were accepted. I think it is because of the session being correct. So did the cookie hash and form hash match, yes they did. Was my session valid, yes it was. Hence everything proceeded to add twice.

I think, and I really don't know what I am talking about really, it is caused because my session is not regenerated. So Session ok, cookie matches form, so do the second post.

So it is not a race condition, but I think you are also right. That if the session hash is encoded in a cookie, and is sent and checked for authorization (post authentication), why set the cookie for csrf at all? Surely it would be much better off in the session table as you said. Does the posted CSRF hash match the stored session CSRF hash would be the only question? If so change the session CSRF hash and make it available for the next hidden input field. The second post coming in would then fail.

So why is there a CSRF cookie at all? As I said I am getting confused, because now the entire CSRF cookie thing seems wrong, if not entirely pointless if I can double post.

As I already said, I really don't know, but I have had an interesting read around it at least.

Thanks for the input, at least I have a better idea of the problem now.

Paul

PS It might be cookie based because of Ajax requests, but they get buggered up and confused by regenerated csrf hash's anyway.
There is an interesting attempt at answering this here http://stackoverflow.com/questions/20504...in-cookies but although easy to follow it is still not entirely clear to me why a csrf token is needed as well. As far as I can tell a successful ajax csrf protected response should include the next csrf token to be used, allowing for stringed requests. But I think I have to read more about this.
Reply
#8

Just remember, there is a difference between cookie data and session data. Session data lives on the server, and cookie data lives in the cookie's value, which is stored in the browser, and only sent back to the server on every request.
Reply
#9

Ah, I think the clouds have lifted (finally). And of course they are all very different things.

1. CSRF
Yes, the CSRF token has to be posted in the form and then compared to the cookie, to see if the source of the form is genuine or not. If the source is not genuine they will not match. This has nothing to do with authorization or sessions.

2. Double clicking
Double clicking or double posting has nothing to do with CSRF. It is an entirely separate issue.

3. Sessions
Sessions are purely about authorisation and have nothing to do with authentication or csrf.

4. Authentication
Has nothing to do with authorisation, csrf or double clicking :-)

I never realized before how important cookies actually are, and how important browser security of those cookies is either.

Anyway, I am much clearer on the whole issue now, thanks Skunkbad for your input, comments and thoughts. I am really quite delighted with Firefox now and have made it my default. Will do the same in the office tomorrow.

Best wishes,

Paul.
Reply
#10

(This post was last modified: 06-20-2016, 11:19 AM by spjonez.)

Code:
$config['csrf_regenerate'] = TRUE;


Disable this setting, it adds nothing to security and will only make your life harder. Especially if you use a lot of AJAX or concurrent AJAX requests.
Reply




Theme © iAndrew 2016 - Forum software by © MyBB