Harmster Posted June 6, 2013 Share Posted June 6, 2013 Hey, The Form API has CSRF protection build in, but if you for some reason don't want to use the API you can however use the CSRF protection. Its very simple but it took some time for me to find out, so i figured i share my findings with the rest. What is CSRF? First you need to create a token and a token name you do that as following: $tokenName = $this->session->CSRF->getTokenName(); $tokenValue = $this->session->CSRF->getTokenValue(); Very simple. Now what you want to do is create a hidden input field like this: $html .= '<input type="hidden" id="_post_token" name="' . $tokenName . '" value="' . $tokenValue . '"/>'; Now this will generate something that will look like this: <input type="hidden" id="_post_token" name="TOKEN1470842875" value="fe8ce9c1b9e6b9e361830df3525c49317a35332fbf626aa8793777a3b705824a"> You are done on the form side. You can now go to the part where you are receiving the post. Then use: $session->CSRF->validate(); This will return true (1) on a valid request and an exception on a bad request. You can test this out to open up your Firebug/Chrome debug console and change the value of the textbox to something else. Basicly what this does is set a session variable with a name (getTokenName) and gives it a hashed value. If a request has a token in it it has to have the same value or it is not send from the correct form. Well I hope I helped someone. 34 Link to comment Share on other sites More sharing options...
arjen Posted June 6, 2013 Share Posted June 6, 2013 Thanks for sharing, harmster! Link to comment Share on other sites More sharing options...
Valery Posted September 5, 2013 Share Posted September 5, 2013 Hi harmster, Thanks for the nice tip. However, I get "Error: Exception: This request was aborted because it appears to be forged. (in /home/.../wire/core/SessionCSRF.php line 80)" in errors.log when I try to spoof the token. Is there any way to catch this exception? Thanks. Link to comment Share on other sites More sharing options...
ryan Posted September 7, 2013 Share Posted September 7, 2013 You can always use a try { ... } catch(Exception $e) { ... } but I think it's probably better to leave that as a fatal condition and stop processing. 1 Link to comment Share on other sites More sharing options...
Valery Posted September 8, 2013 Share Posted September 8, 2013 Thank you, Ryan. My intent was to implement some sort of security auditing such that I would receive a message whenever a CSRF was attempted. So I needed to catch the exception. The following code works for me. try { $form->processInput($input->post); $session->CSRF->validate(); } catch (WireCSRFException $e) { echo "Processing aborted. Suspected attempt to forge the submission. IP logged." . $e->getMessage(); /* * Some code to execute whenever the token gets spoofed * Like sending a notification mail with the spoofer's IP address */ die(); // live a great life and die() gracefully. } 5 Link to comment Share on other sites More sharing options...
valan Posted September 24, 2013 Share Posted September 24, 2013 Thanks for hint! Is it possible to handle several forms at one page using these CSRF protection methods? I've used code above for two forms and got identical token names and values... Thanks! Link to comment Share on other sites More sharing options...
Harmster Posted September 24, 2013 Author Share Posted September 24, 2013 Yeah, that shouldn't matter, the token is there to prevent others from sending their forms to your backend. Having 2 the same tokens is OK. The tokens aren't there to indentify your form. Source: http://stackoverflow.com/questions/14715250/generating-csrf-tokens-for-multiple-forms-on-a-single-page Link to comment Share on other sites More sharing options...
valan Posted September 25, 2013 Share Posted September 25, 2013 Yeah, got it. Thanks! Link to comment Share on other sites More sharing options...
Hari KT Posted June 20, 2014 Share Posted June 20, 2014 Awesome, and thanks for sharing. Link to comment Share on other sites More sharing options...
Can Posted July 25, 2014 Share Posted July 25, 2014 (edited) That's great, thank you! Just changed the value via Chrome Dev-Tools as you mentioned to see what happens I get a white screen with this one "Unable to complete this request due to an error. Error has been logged." <?php public function validate() { if(!$this->config->protectCSRF) return true; if($this->hasValidToken()) return true; $this->resetToken(); throw new WireException($this->_('This request was aborted because it appears to be forged.')); } Aaaaha, just got it working. used the hasValidToken() function instead. In my login form it looks like this now <?php if($input->post->username && $input->post->pass && $session->CSRF->hasValidToken() == true) So for anyone struggling like me ;-) You can then of course check easily for invalid tokens ( hasValidToken() == false ) and throw custom errors in this case if you like. cheers Update: Looks like I'm doing something wrong. Even after I noticed $session->CSRF->resetToken(); But I'm sure I'll find it out Update 2: Thanks to Valery I think I got it now. got rid of the hasValidToken() == true thing an am using try and catch now <?php try { $session->CSRF->validate(); //SWIFTMailer stuff to send the mail } catch (WireCSRFException $e) { $error = "Seems to be a resubmission."; } Edited July 26, 2014 by Can 1 Link to comment Share on other sites More sharing options...
Valery Posted August 21, 2014 Share Posted August 21, 2014 Hi fellow ProcessWire devs, Recently I had a revelation which was: CSRF session IDs do expire in ProcessWire with time (though I do not yet know how soon it is). So, for instance, if you leave a form unsubmitted for some time and then try to post it, ProcessWire will through a CSRF validation error. I gave this a little bit of thought and came up with some code like this: public function processForm ($form) { try { $form->processInput(wire('input')->post); wire('session')->CSRF->validate(); } catch (WireCSRFException $e) { die($this->displayForm($form)); // $this->displayForm($form) is a method which returns $form->render() } } I run a few public forms for comments and orders, and I do not want to piss off my guests and clients when they have to re-enter form data again. If their CSRF session runs out, I will send them their form back with their data filled in. What do you think of this approach? How likely is that a CSRF session expiry occurs? Should I be harsh when a CSRF validation fails, or should I give them a second try? Link to comment Share on other sites More sharing options...
adrian Posted August 21, 2014 Share Posted August 21, 2014 Are you running 2.4 stable? Does this dev commit fix the issue for you: https://github.com/ryancramerdesign/ProcessWire/commit/c18cf1e62eaafb3df1071a8c0936e5346721f754 Link to comment Share on other sites More sharing options...
Valery Posted August 21, 2014 Share Posted August 21, 2014 Thanks, adrian. Cannot really say for sure if this commit addresses my issue but I found that the CSRF challenge cookie lasts a whopping 30 days. // set challenge cookie to last 30 days (should be longer than any session would feasibly last) setcookie(session_name() . '_challenge', $challenge, time()+60*60*24*30, '/', null, false, true); Now that's really more than enough time to think a form submission over and over a few hundred times Link to comment Share on other sites More sharing options...
Juergen Posted September 30, 2015 Share Posted September 30, 2015 Hello @ all, today I have tried to implement the CSRF validation for the first time on a contact form. I have implemented the tokens to the form in a hidden field. So far so good, but the validation shows always "1" after sucessfull or failed submission. You can now go to the part where you are receiving the post. Then use: $session->CSRF->validate();This will return true (1) on a valid request and an exception on a bad request. You can test this out to open up your Firebug/Chrome debug console and change the value of the textbox to something else. I have a custom form with Bootstrap 3 Markup and this is the code that runs after there are no errors in the form: if($session->CSRF->validate() == "1"){ //send the data and create the success message $thankyou = __("danke nachricht"); $mailsenttext = __("Nachricht erhalten"); $out = "<div class='alert alert-success' role='alert'><span class='fa fa-check fa-3x pull-left fa-border'></span><h4>$thankyou</h4><p>$mailsenttext</p><div class='clearfix'></div></div>"; //create the email $fullname = $firstname . ' ' . $lastname; $userip = $_SERVER['REMOTE_ADDR']; $mail = new PHPMailer(); $mail->IsHTML(true); $registertext = __("Mailheader Contactform"); $senderinfo = __("Absender Label"); $mail->From = $email; $mail->FromName = $fullname; $mail->AddAddress($emailTo); $mail->AddReplyTo($email, $fullname); $mail->Subject = $subject; $mail->Body .= "<p><b>$subjectlabel:</b> $subject</p><p><b>$messagelabel</b><br />$comments</p><p><b>$senderinfo</b><br />$gendername $firstname $lastname<br />$email<br />IP: $userip</p>"; $mail->send(); $form = ""; } else { //dont send data and create an error message $out = "<div class='alert alert-danger' role='alert'><span class='fa fa-warning fa-3x pull-left fa-border'></span><h4>$warning</h4><p>$doublesubmissiontext</p><div class='clearfix'></div></div>"; } Unfortunately the value of the $session->CSRF->validate() is always "1", even after successfull submission, so the form could be sent multiple times. What I am doing wrong? Best regards Jürgen Link to comment Share on other sites More sharing options...
LostKobrakai Posted September 30, 2015 Share Posted September 30, 2015 CSRF is not there to protect your users from multiple submissions. It's there to ensure that the data send are from the exact user you send the form to, nothing more. If you additionally want to prevent multiple submissions, than use $session->CSRF->resetToken(); on successful submissions. But this would prevent other forms, that the users may already have requested on e.g. another tab, from working. There are other ways to prevent duplicate form submissions, like using redirects. E.g. one way with redirects would be: /my-page-with-form/ -> submits to "submit/" /my-page-with-form/submit/ -> on success redirect back, on error save submitted data, too, to repopulate form …, but you could also simply redirect to the same page in your current code just after $mail->send(); 2 Link to comment Share on other sites More sharing options...
Juergen Posted September 30, 2015 Share Posted September 30, 2015 Thanks, I have misunterstood something completely, so I will redirect after $mail->send(); Best regards Link to comment Share on other sites More sharing options...
LostKobrakai Posted September 30, 2015 Share Posted September 30, 2015 But keep in mind that this will only solve the issue of people hitting refresh after an successful submission (and redirect). I doesn't help if someone is hitting refresh while submitting the form, which will still send two requests. 1 Link to comment Share on other sites More sharing options...
Juergen Posted October 1, 2015 Share Posted October 1, 2015 Thanks LostKobrakai, now I solved it with a session id in an hidden input field that will be compared with the post value of the hidden field after submission. If session value and post value are same then send the form data and remove the session id. If someone hits the F5 button after submission, the valid session id is no more longer available (because it was removed after submission) and so the values dont match any longer. As a result a hint for "double submission" appear on the screen instead of submitting the form. The reason why I missunderstood the CSRF was a post by Soma in another topic where he uses CSRF to prevent double submissions. https://processwire.com/talk/topic/3633-prevent-form-resubmission/?p=35567 Best regards Link to comment Share on other sites More sharing options...
adrianmak Posted January 22, 2016 Share Posted January 22, 2016 I added form CSRF in my search form <form method='post' action='<?php echo $config->urls->root; ?>search/'> <input type='hidden' id='_post_token' name='<?php $this->session->CSRF->getTokenName(); ?>' value='<?php $this->session->CSRF->getTokenValue(); ?>' /> <input class='search__input' type='text' name='search_keywords' id='search_keywords' value='' /> <select class='search__select' id='type' name='type'> <option value=''>All</option> <?php foreach($pages->get(1028)->children() as $category) { if($t==$category) $selected = "selected"; else $selected = ""; echo "<option value='{$category->id}' {$selected}>{$category->title}</option>"; } ?> </select> <!--<input type='text' name='typeoptions' id='typeoptions'>--> <span class='search_options'></span> <input class='search__button' type='submit' value='Search' /> </form> But, the outputted two values of hidden field are empty Link to comment Share on other sites More sharing options...
kongondo Posted January 22, 2016 Share Posted January 22, 2016 (edited) Looks like you are in a template file rather than a module. In that case, PHP does not know what $this is. Turn debug mode on to catch such errors...Change code as follows: $session->CSRF->getTokenName(); $session->CSRF->getTokenValue(); Edit: ..and you also need to echo out your token name and values Edited January 28, 2016 by kongondo Link to comment Share on other sites More sharing options...
adrianmak Posted January 22, 2016 Share Posted January 22, 2016 Looks like you are in a template file rather than a module. In that case, PHP does not know what $this is. Turn debug mode on to catch such errors...Change code as follows: $session->CSRF->getTokenName(); $session->CSRF->getTokenValue(); now works! Another problem I found immediately, I opened firbug to change textfield value or a select option value, it still success to process Did I test CSRF the right way? search.php try { $q = $sanitizer->text($input->post->search_keywords); $t = $sanitizer->text($input->post->type); $o = $sanitizer->text($input->post->typeoptions); $session->CSRF->validate(); } catch(WireCSRFException $e) { echo "Processing aborted."; die(); } // if success, continue to process form data Link to comment Share on other sites More sharing options...
Manol Posted February 19, 2016 Share Posted February 19, 2016 How would I use CSRF with ajax in a page that's located in a different server. I would like to use it with phonegap/cordova, make and ajax(angular) call from the phone to get some data back. Link to comment Share on other sites More sharing options...
Manol Posted February 19, 2016 Share Posted February 19, 2016 I've in my php file <?php header('Access-Control-Allow-Origin: *'); /* Allowed request method */ header("Access-Control-Allow-Methods: PUT"); /* Allowed custom header */ header("Access-Control-Allow-Headers: Content-Type"); when I make calls with ajax(angular), it works this.login = function (creds) { var deferred = $q.defer(); $http({ method: 'POST', url: 'http://miwebpage.com/api/login/', data: {'user': creds.username, 'pass': creds.password} }) .success(function (result) { deferred.resolve(result); }) .error(function(data){ deferred.reject; }); return deferred.promise; } I would like to use a token in every call (csrf?), how can I achieve that in ajax ( angular ), any tutorials? I've searched but not sure yet how to do it with pw and csrf. Link to comment Share on other sites More sharing options...
Chris Posted February 22, 2016 Share Posted February 22, 2016 Thanks a lot for this hint! Link to comment Share on other sites More sharing options...
adrianmak Posted May 18, 2017 Share Posted May 18, 2017 How could i use different token name and value on different form on the same page ? like Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now