I have a multiple form wizard, with multiple steps that need to be completed, the script prevents the user filled data from being lost and prevents the user step from being lost.
The script steps are saved in a session that avoids going back to the beginning, always keeping them in the user's current step.
But I have a vulnerability problem in the process of the steps that need to be followed.
If a malicious user changes the URL values, as follows:
localhost/wizard/saveTemp.php?step=6
That malicious user can skip the steps at will, thus circumventing the system.
Example:
The user is in the step 1
, but I modify the URL
and automatically jump to the step 6
.
Note: The second way to circumvent the system is by modifying the code of the &
HTML
links by means of the adding or code . Example: just like modifying the URLs automatically jump to step .atrás
continuar
multi form wizard
consola
atributos
javascript
<a href="#" class="next" onclick="show_step('6')>Continuar</a>
PHP
6
Then,
How can I prevent the system from being mocked by adding a hardened control using
PHP
, where a user cannot go from step1
to step3
or to step6
, but must continue the order of steps1, 2, 3, 4, 5, 6
and so on.How can I validate the fields of the wizard form with
PHP
the set of existing validations with Javascript?
My code
ScriptWizard
<?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
Note: Comments have been received saying that the procedure is wrong, that you cannot pass data through
POST
andGET
actually take it now it would be an unknown to me?
before those comments, this procedure was taken by a response from Xerif
<?php
session_start();
$step = isset($_GET['step']) ? $_GET['step'] : 1;
// We save the form data in a session variable
$_SESSION['datos_form'] = $_POST;
// we also add the step to the array, you can not use this name (__step__) as name in the form
$datosForm = (isset($_SESSION['datos_form']) && is_array($_SESSION['datos_form'])) ? $_SESSION['datos_form'] :array();
$sStep = isset($datosForm['__step__']) ? $datosForm['__step__'] : 1;
$step = isset($step) ? $step : $sStep;
$radio = isset($datosForm['radio']) ? $datosForm['radio'] : 1;
$_SESSION['datos_form']['__step__'] = $step;
header('Content-Type: application/json');
$json = array(
'radio' => $radio,
'step' => $step
);
echo json_encode($json);
Process:
So since we already have the steps defined, in the variable
$step
in asesión
, when the next step/send is being sent/processed, check that it$step
is the next step.Or maybe you are interested in encrypting the values sent by method
GET
, in the following sources you can analyze.Other topics of interest
Creating a wizard using
PHP
it that works in the same way as the scriptjQuery
is really impossible to carry out the same mechanism, and when doing so, the data would have to be sent to the same page, I don't think that procedure is the desired one.Imagining that a user has disabled the use of
javascript
in the browser, all the steps would be shown at the same time, as if it were a single one,form
although in reality it is, only that in the use of the scriptjQuery
they are divided into parts.Then the validation of the form using
PHP
it would be in a normal way, that when executing the send with empty data, execute the errors for eachinput
empty field.The steps don't matter, nor the order of the steps, nor anything that happens on the client side. What happens in the browser/browser cannot be trusted. If you are going to work as a web developer, you must understand that what happens in the web client cannot be trusted . Therefore, the only way to solve this "mistrust" problem is to: Revalidate all data when it is received on the server. In other words, validate all the data at the same time (those recovered in the 6 steps) after finishing the wizard and before accepting the operation and saving the data in the database .
Essentially this is a feature of the Client-Server architecture.
It doesn't matter if you're working in PHP, C# or Node.js.
Client-side validations are to make the interface "friendly" to the user and should be user-focused. They are essential for the so-called "user experience" and there is not much to do with malicious users, ultimately they can attack your system without using the web interface, using a PostMan-like tool calling PHP scripts directly.
In contrast, server-side validations are to check that the data is complete and valid. This includes all the validations that you as an architect decide are necessary. Eg, validate the format of the "email" field or not.
I repeat, they are different validations with different purposes, they are also different applications. There is no basis for code reuse here.
Save the step it takes in a session. and when taking the next step, verify that it is correct. Request that value and update it through ajax.
Although if you think it would be a lot of work for the server. You can create an array with a single constant element. That element will save the step where you are. changing "step" removes the previous constant variable and replaces it with the current step.
In pseudocode it would be:
I hope it is understood. This way you don't need ajax, and you can still use GET or POST.
You can pass the values of the step by POST or use the router of your framework to force requests to that url that are only by POST, otherwise redirect to step 1.