I have a multi form wizard, with several steps that must be completed, this script has been developed very different from the normal ones, my script avoids that the data filled by the user is lost and it avoids losing the step of the user.
The steps of the script are saved in a session that avoids returning to the beginning, keeping them always in the current step of the user.
But I have a vulnerability problem in the process of the steps that must be followed.
If a malicious user changes the values of the URL, in the following way:
localhost/wizard/saveTemp.php?step=6
That user can skip the steps at your will.
Example the user is in step 1, but I modify the url and automatically jump to step 6.
So how can I prevent them from teasing the system by adding a strengthened control with PHP, where a user can not jump from step 1 to step 3 or step 6, but must continue the order of steps 1,2,3,4,5 , 6 and so on.
My Script Wizard
<?php session_start(); if (isset($_GET['p'])) { session_destroy(); session_start(); } ?> <script> var currentStep = <?php echo $step ?>; // Variable indicating the current step, data selected with PHP sessions var radio = <?php echo $radio ?>; //Value of the radius selected function show_step(step) { var data = $("#form").serialize(); var url = 'saveTemp.php?step=' + step; var valid = true; // [OPTIONAL] Validate only if you are going forward if (currentStep < step) { // We search all the fields within the current step. $('#step' + currentStep).find('input,textarea,select').each((idx, el) => { let $field = $(el); let $fieldCont = $field.closest('.form-group'); // If the field is a checkbox or a radio and an option was not selected if (($field.prop('type') == 'checkbox' || $field.prop('type') == 'radio') && !$fieldCont.find('input[name="'+$field.prop('name')+'"]:checked').length) { $fieldCont.addClass('error'); valid = false; } // If the field is NOT a checkbox or a radio and is empty else if ($field.prop('type') != 'checkbox' && $field.prop('type') != 'radio' && !$field.val()) { $fieldCont.addClass('error'); valid = false; } else { $fieldCont.removeClass('error'); } }); } // If at least one field was not completed if (!valid) { return; } // $.ajax({ type: "POST", url: url, data: data }).done(function(resp){ $('#radio').val(resp.radio); if (step === 2) { var radio = parseInt(resp.radio); switch(radio) { case 1: urlform = './app/themes/pay_paypal.php' break; case 2: urlform = './app/themes/pay_paypal2.php' break; case 3: urlform = './app/themes/pay_paypal3.php' break; default: urlform = './app/themes/pay_paypal4.php' break; } $('#divPay').load(urlform,function(responseTxt, statusTxt, xhr){ if(statusTxt === "success") { $('#step' + currentStep).css("display", "none"); $('#step' + step).fadeIn("slow"); currentStep = step; } if(statusTxt === "error") { // } }); } else { $('#step' + currentStep).css("display", "none"); $('#step' + step).fadeIn("slow"); currentStep = step; } }); }; $(function() { show_step(currentStep); $('a.next').click(e => { e.preventDefault(); show_step(currentStep + 1); }); $('a.back').click(e => { e.preventDefault(); show_step(currentStep - 1); }); }); </script>
HTML structure
<form id="form"> <div id="step1" class="step"> <h1>Step 1</h1> <a href="#next" class="next">next</a> </div> <div id="step2" class="step"> <h1>Step 2</h1> <a href="#back" class="back">back</a> <a href="#next" class="next">next</a> </div> <div id="step3" class="step"> <h1>Step 3</h1> <a href="#back" class="back">back</a> <a href="#next" class="next">next</a> </div> <div id="step4" class="step"> <h1>Step 3</h1> <a href="#back" class="back">back</a> <a href="#next" class="next">next</a> </div> <div id="step5" class="step"> <h1>Step 3</h1> <a href="#back" class="back">back</a> <a href="#next" class="next">next</a> </div> <div id="step6" class="step"> <h1>Step 4</h1> <a href="#back" class="back">back</a> </div> </form>
saveTemp.php Or it is possible to receive the steps by POST method instead of the GET method, thus avoiding changes in the URL (saveTemp.php? Step = 6)
<?php session_start(); $step = isset($_GET['step']) ? $_GET['step'] : 1; // We save the form data in a session variable $_SESSION['data_form'] = $_POST; // we also add the step to the array, you can not use this name (__step__) as name in the form $dataForm = (isset($_SESSION['data_form']) && is_array($_SESSION['data_form'])) ? $_SESSION['data_form'] :array(); $sStep = isset($dataForm['__step__']) ? $dataForm['__step__'] : 1; $step = isset($step) ? $step : $sStep; $radio = isset($dataForm['radio']) ? $dataForm['radio'] : 1; $_SESSION['data_form']['__step__'] = $step; header('Content-Type: application/json'); $json = array( 'radio' => $radio, 'step' => $step ); echo json_encode($json);
2 Answers
Answers 1
When you complete processing the form, put the step in a session variable. When you're processing the next submission, check that $step
is the next step.
$last_step = isset($_SESSION['step']) ? $_SESSION['step'] : 0; if ($step > $last_step + 1) { die("You can't jump directly from step $last_step to $step"); }
At the end of the script, once you've validated all the information, save the step in the session variable:
$_SESSION['step'] = $step;
Full code:
<?php session_start(); $step = isset($_GET['step']) ? $_GET['step'] : 1; $last_step = isset($_SESSION['data_form']['__step__']) ? $_SESSION['data_form']['__step__'] : 0; if ($step > $last_step + 1) { die(json_encode(array('error' => 'Invalid step'))); } // We save the form data in a session variable $_SESSION['data_form'] = $_POST; // we also add the step to the array, you can not use this name (__step__) as name in the form $dataForm = (isset($_SESSION['data_form']) && is_array($_SESSION['data_form'])) ? $_SESSION['data_form'] :array(); $sStep = isset($dataForm['__step__']) ? $dataForm['__step__'] : 1; $step = isset($step) ? $step : $sStep; $radio = isset($dataForm['radio']) ? $dataForm['radio'] : 1; $_SESSION['data_form']['__step__'] = $step; header('Content-Type: application/json'); $json = array( 'radio' => $radio, 'step' => $step ); echo json_encode($json);
Answers 2
I would suggest you to simply don't do so.
I mean: I figure out you would also validate user inputs and don't permit users go ahead to next step if current one is not properly filled.
And I also figure out you validate inputs at least server side.
Having that no subforms would be valid by default (without any user input) or, if so, you can simply add a hidden input and fill it automatically on some event (such as form-step loaded); the only thing you need to do is to re-validate whole form (up to required step) every time any step is requested.
If client requests, for example, step 6 but step 5 was not validated, just send http-redirect header to step 5 and, optionally, some minimal html error suggesting to do just that and providing a link to do so manually.
0 comments:
Post a Comment