Prevent Double Form Submit using Tokens - php

I am trying to prevent the user from double submitting the forum by adding token hidden field.
So here is what I have done so far (before the forum loads I have this code to create a token with the current time as a value.
$token = time();
setcookie('formToken', $token, time() + 3600);
in my forum I have a hidden input like this
<form method="post" action="'.$PHP_SELF.'?action=update">
<input type="hidden" name="token" value="'.$token.'" />
<input type="submit" value="go" />
</form>
now on the top of my page where $action == "update" I have this code
if(isset($_POST) && ($_POST['token'] != $_COOKIE['formToken'])){
$error_list .= '<li>You can not submit this forum twise.</li>';
}
if i hit F5 to refresh the page it submit the form again without displaying my error.

I suggest you to use use the PRG pattern (Post/Redirect/Get), which is also implemented by forums like phpbb.
Post/Redirect/Get (PRG) is a web development design pattern that
prevents some duplicate form submissions, creating a more intuitive
interface for user agents (users). PRG implements bookmarks and the
refresh button in a predictable way that does not create duplicate
form submissions.

gd1 answer will not prevent double click submission or accidental double submit by various jQuery bindings on a complex javascript form code.
Double click may be even faster then disabling submit button, or hiding it with javascript, so this would not be a full answer either.
The session token will not work either because session is not yet written and thus available or updated for the second process which may be just milliseconds away sharing the same session ID. The session is stored only upon completion of the fist process.
Cookie technique could be an answer as far as both processes are able to communicate over cookie in a blocking way, which may result to the same problems as the session sharing above.
The best solution would be to use server's shared memory access to check if the other process had already processed the data (order, payment, etc..) with the pregenerated data hash, or use database table blocking select and insert to check if the pregenerated hash has been already submitted.

Why not just set a session when the form is successfully submitted?
so $_SESSION['submitted'] = 1;
Then you can check for it.
Or Do
if(isset($_POST['submit']) && ($_POST['token'] != $_COOKIE['formToken'])){
$error_list .= '<li>You can not submit this forum twice.</li>';
}

Suggestion 1)
on Successful Submission Delete the cookies (removeTokens)
function removeToken()
{
//set formToken cookie val to "" (or any default xxxx) and the past expiry date for it
setcookie("formToken", "", time()-3600);
//try to unset - this is not needed ,we may try it
unset($_COOKIE['formToken']);
}
ie simply on your page if(isset($_POST)) removeToken();
Suggestion 2)
Perform a redirect as suggested by Tom Wright here Avoiding form resubmit in php when pressing f5
header('Location: formsubmitSucess.php');

I use this way of preventing double form submissions, it has worked on all occasions so far. Let me know if you need additional questions as this tutorial assumes you have intermediate knowledge on database and PHP.
STEP 1 : add a field on your database like this:
replace YOUR-TABLE with the name of your database table.
ALTER TABLE `YOUR-TABLE` ADD `token` VARCHAR(35) NULL DEFAULT NULL AFTER `creationtoken`, ADD UNIQUE (`token`) ;
STEP 2 on your form page you add this to the very first line:
it will create a unique toke that will be inserted into your database table along with you query, so that it can be checked for later to make sure no other like it is submitted into your database, meaning no double form submissions.
<?php
session_start();
date_default_timezone_set('America/Chicago');
$_SESSION['token'] = md5(session_id() . time());
?>
then just before your submit button add this:
// add this before the submit button
// this will post the unique token to the processing page.
<div style="width:100%; color:#C00; font-weight:normal;">Session Token: <?php echo strtolower($_SESSION['token']) ?></div>
<input type="hidden" name="token" id="token" value="<?php echo $_SESSION['token']?>" />
// add this before the submit button
<input type="submit" id="submit" name="submit" class="button" value="Submit" />
STEP 3: now on your process.php page
//this is where all of your form processing takes place.
// this is where you call the database
// if you need the database file let me know...
include("../common/databaseclass.php");
$db= new database();
//here the token is posted then the database table is checked and
//if the form has already been added it will return a 1 and will
//cause the query to die and echo the error message.
$token = $_POST['token'];
$query = "SELECT token FROM YOURTABLE WHERE token = '$token' LIMIT 1";
$result = $db->query($query);
$num = mysql_num_rows($result);
if ($num>0) {die('your form has already been submitted, thank you');}
else {
$host = "localhost";
$user = "user";
$pass = "password";
$db_name = "database";
mysql_connect($host,$user,$pass);
#mysql_select_db($db_name) or die( "Unable to select database");
// table query
$sql1="INSERT INTO YOURTABLE (
`token`,
`user`,
`email`,
`password`,
`newaccount`,
`zipcode`,
`city`,
`state`,
`country`,
`telephone`,
`creationip`,
`createdaccount`
)
VALUES (
'$token',
'$username',
'$email',
'$password',
'$newaccount',
'$zipcode',
'$city',
'$state',
'$country',
'$phone',
'$ipadress',
'$createdaccount'
)";
$db->query($sql1);
header("location:" http://home.php ");
}

For the same issue I made a code to use it for my own stuff. It has the PRG pattern and flexible to use it on same page or with extern PHP file for redirection - Easy to use and safe, maybe this might help you.
class unPOSTer {
private
$post = "KEEP_POST";
public function __construct(string $name = null) {
if (version_compare(PHP_VERSION, "5.4.0") >= 0) {
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
} else {
if (!$_SESSION) {
session_start();
}
}
$this->post = $name;
}
public function unPost() {
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
} elseif (strcasecmp($_SERVER["REQUEST_METHOD"],"POST") === 0) {
$_SESSION[$this->post] = $_POST;
header("Location: " . $_SERVER["PHP_SELF"] . "?" . $_SERVER["QUERY_STRING"]);
exit;
} elseif (isset($_SESSION[$this->post])) {
$_POST = $_SESSION[$this->post];
}
}
public function retrieve($data) {
if (isset($_SESSION[$this->post])) {
$posts = #$_SESSION[$this->post][$data];
if (isset($posts)) {
return $posts;
} else {
return null;
}
}
}
public function reset() {
if (isset($_SESSION[$this->post])) {
unset($_SESSION[$this->post]);
}
}
}
Then use it like this:
<?php
require_once "unPOSTer.class.php";
$unpost = new unPOSTer();
$unpost->unPost();
?>
<form action='' method=POST>
<input type=text name=fname value="<?php echo $unpost->retrieve("fname"); ?>" placeholder="First Name">
<input type=text name=lname value="<?php echo $unpost->retrieve("lname"); ?>" placeholder="Last Name">
<input type=submit name=send value=Send>
</form>
<?php echo $unpost->reset(); ?>
Not much to configure, do it on every page you send form data if you like. The retrieve() method spits out the data you have sent, in case if you might go back and fix something. Feel free to fork/pull it at my GitHub page I added 2 demos there.

I had the same problem, here is a simple fix:
if(!empty($_SESSION['form_token']) && time() - $_SESSION['form_token'] < 3){
$data['message'] = 'try again later';
return;
}
$_SESSION['form_token'] = time();
In my case the PRG pattern didn't have any effect since form submitted multiple times at the same time and the code had not been executed and there is no data saved to compare it against.

Related

How to use session variables in different pages, when the "session_start()" function is already being used?

I have a problem trying to display some session variables in a different page in php. Most of the threads I've seen here are related to the lack of "session_start()". But I already have it at the top of my files.
I'm trying to create a "recover your password module".
I have a form element which has an action set to another page.
I have a database with two tables. The form just allows the user to input his e-mail. When I click the submit button, a mysqli connection is supposed to look in both tables for the user with the e-mail stored in the form and then store the rest of the elements (name, password, recovery question and its answer) of the table in some php variables.
In an if conditional statement I check which table the e-mail is stored in and then create some session variables that store the whole information of said user.
In the other page I'm just trying to echo some text along with the session variable of the name of the user if that variable session is set. If not it will echo a "failed session" text.
Here is the form (the file where this thing is has the session_start() already in top):
<form action="cambio.php" method="post">
<h1>RECUPERACIÓN DE CONTRASEÑA</h1><br>
<p>Escriba su dirección de correo para modificar su contraseña:</p><br>
<div class="form group">
<label for="email">Correo electrónico:</label>
<input type="email" class="form-control ancho" id="email" name="email" class="ancho"><br>
</div>
<button type="submit" class="btn btn-primary" name="recuperar">Recuperar contraseña</button>
</form>
After connecting to the database, this is where I'm setting the session variables (in the same .php as the previous form code):
if(isset($_POST['recuperar'])) {
$c = $_POST["email"];
$buscarAlumno="select nombre, correo, password, num_pregunta, respuesta from alumno where correo='$c';"; //first table
$buscarProfesor="select nombre, correo, password num_pregunta, respuesta from profesor where correo='$c';"; //second table
$resA = $conn->query($buscarAlumno);
$resP = $conn->query($buscarProfesor);
$rowA = $resA->fetch_array();
$rowP = $resP->fetch_array();
//Save the e-mail-matching fields (first table)
$rnA = $rowA["nombre"];
$rcA = $rowA["correo"];
$rpA = $rowA["password"];
$rnpA = $rowA["pregunta"];
$rrA = $rowA["respuesta"];
//Save the e-mail-matching fields (second table)
$rnP = $rowP["nombre"];
$rcP = $rowP["correo"];
$rpP = $rowP["password"];
$rnpP = $rowP["num_pregunta"];
$rrP = $rowP["respuesta"];
//In this statement, some of the variables above are going
//to be stored in session variables depending on the
//table in which the e-mail was found
if (isset($c)) {
if ($c == $rcP) {
$_SESSION['nomCorrecto'] = $rnP;
$_SESSION['corCorrecto'] = $rcP;
$_SESSION['pasCorrecto'] = $rpP;
$_SESSION['preCorrecto'] = $rnpP;
$_SESSION['resCorrecto'] = $rrP;
}
elseif ($c == $rcA) {
$_SESSION['nomCorrecto'] = $rnA;
$_SESSION['corCorrecto'] = $rcA;
$_SESSION['pasCorrecto'] = $rpA;
$_SESSION['preCorrecto'] = $rnpA;
$_SESSION['resCorrecto'] = $rrA;
}
else {
echo "Usuario no registrado.";
}
}
}
This code is in another page, where I want the results to be show (again, it already has the session_start() on top of the file).
<?php
//This part should show the name of the user, but is not working
if(isset($_SESSION['nomCorrecto'])) {
echo "Hola " .$_SESSION['nomCorrecto']. ".";
}
else {
echo "Sesión fallida.";
}
?>
I don't know what I'm doing wrong, I don't know if the error is about the form statement or how I'm saving and echoing the session variables.
I just get the "Sesión fallida" error text.
Any help is welcome.
Thanks in advance.
Ensure that you've initialize session_start(); before any sessions are being called.
For example, you can add something like this to the beginning of each page where session is used: if (session_status() == PHP_SESSION_NONE) session_start(); This will check whether the session is started or not, and if not it will start it.
You can also use var_dump($_SESSION); at every places where you use sessions in order to track / examine the session data.
Note: The common reason why your code is not working is if you failed to initialize session_start(); in places necessary. Failure to carry out some necessary checks on your code can also cause this. You can find more detail about session loss in the answer of this question.
Finally figured it out. I need to declare the session variables that I'm going to use right after the session_star() function.
Thanks for the help.

Row is inserted into database everytime I refresh the page

Every time I try to refresh the page I get a new row. I tried to read many posts regarding to this problem, but I couldn't do anything since I'm new in database programming.
I don't know where the value come from, because the same value is repeated over and over.
My code.
<?php
require('connect.php');
$sql="CREATE TABLE test(id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
user VARCHAR(25),message LONGTEXT)";
if($sql==true){
$res=mysql_query( $sql);
}
?>
<?php
$user=null;
$message=null;
if(isset($_POST['user'])){
$user=$_POST['user'];
}
if(isset($_POST['message'])){
$message=$_POST['message'];
}
if(!empty($_POST)){
if($user&&$message){
$insert=mysql_query("INSERT INTO test(user,message)VALUES('$user','$message')");
}
else{
echo "please fill out the fields";
}
}
?>
<html>
<body>
<form action="database.php" method="post">
<p><label for="user">Name:</label><br/>
<input type="text" name="user" id="user"/></p>
<p><label for="message">Message:</label><br/>
<textarea ="message" name="message"> </textarea></p>
<button type="submit" name="submit" value="send">Send Message:</button>
</form>
<br/><br/><tr><td>The Users Comments:</td><td><br/><br/>
</html>
<?php
$query=mysql_query("SELECT * FROM test ORDER BY id DESC");
while($row=mysql_fetch_ASSOC($query)){
$name=$row["user"];
$message=$row["message"];
echo "username:",$name,'<br/>'," Messages: ",$message,'<br/>','<br/>';
}
?>
Problem is that everytime you refresh the page your browser is re-posting the same data. To workaround it you should consider implementing the Post, Redirect, Get pattern in your page.
Fundamentally this means that upon a successful POST (i.e. row was inserted) you should redirect to another page. This effectively stops the user from having the ability to re-post the same data.
The link above has a good overview of how to implement...
I don't find a problem in your code. Probably when you post data first time and then refresh the page, data is posted again. Most of the browsers like firefox gets confirmation if browser is re-posting data.
Edit:
To avoid this you must use redirect to GET method. see this
try to edit this
Blockquote
if(!isset($_POST)){
if($user && $message){
$insert=mysql_query("INSERT INTO test(user,message)VALUES($user,$message)");
}
and be sure ctrl+f5 then refresh page
Before insert check database if those values are present in database or not
like
if(!empty($_POST)) {
if($user && $message) {
//check if this user and message in present database or not
$query = mysql_query("SELECT * FROM test WHERE user='".$user."' AND message='".$message."'");
$count = mysql_num_rows($query);
if ($count > 0 ){ // if $count is greater than 0 means values exists in database then
echo "Data exists";
}
else {
// insert
$insert=mysql_query("INSERT INTO test(user, message) VALUES ('$user','$message')");
}
}
else {
echo "please fill out the fields";
}
it is just a example you can modified it with your requirement :)

POST doesn't work fine

Well I have a problem with my code:
if ($_POST) {
//send confirmation email (or insert into database, etc...)
if(isset($_POST['del'])) {
$Link = $_POST['del_link'];
$query = "UPDATE comentarios SET del = '1' WHERE id = '".$Link."'";
mysql_query($query) or die ('Error: ' . mysql_error());
//header('Location: http://google.es'); //For debug
}
}
echo '<form name="del" method="post">
<input type="hidden" name="del_link" value="'.$rowComen['id'].'" />
Delete
</form>';
But when I press the link the web refreshes and that's all...
I had tried with: header('Location: http://google.es'); But I don't redirect to google...
And I don't know if the problem is in the post or in the query...
if(isset($_POST['del'])) {
You dont seem to have del form field. so the code inside this if statement is never executed. i think you are trying to check for del_link. so make it as if(isset($_POST['del_link'])) {
Have you checked in your browser if it contains the right value? The form as it is will contain the exact value '.$rowComen['id'].', unless a part of the PHP code is missing and the form is actually inside a string..
[edit]
I see. The form's name is 'del', but that name is never sent. Make the name of your submit button 'del', or add another hidden element. Easier still: Just check for the existence of del_link instead of del:
if(isset($_POST['del_link'])) {
$Link = $_POST['del_link'];

php mysql prevent duplicate inserts from rapid form submits

I'm testing with this code:
<?php
$db = new PDO('mysql:host=localhost;dbname=test', 'root', 'root');
if(filter_has_var(INPUT_POST, 'submit')) {
$r = $db->query('SELECT * FROM test WHERE value="'.$_POST['value'].'"');
$n = $r->rowCount();
for ($i=0; $i<=10000000; $i++) {
// do nothing
}
if (!$n) {
$db->query('INSERT INTO test (id, value) VALUES (NULL, "'.$_POST['value'].'")');
}
}
?>
<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
<input type="text" name="value" value="">
<input type="submit" name="submit" value="Submit">
</form>
When testing the above form in safari I am able to submit the same value into my database multiple times by rapidly clicking the submit button.
But, when I test with this code:
<?php
$db = new PDO('mysql:host=localhost;dbname=test', 'root', 'root');
if(filter_has_var(INPUT_POST, 'submit')) {
for ($i=0; $i<=10000000; $i++) {
// do nothing
}
$r = $db->query('SELECT * FROM test WHERE value="'.$_POST['value'].'"');
$n = $r->rowCount();
if (!$n) {
$db->query('INSERT INTO test (id, value) VALUES (NULL, "'.$_POST['value'].'")');
}
}
?>
The conditional works and I'm unable to submit the same value multiple times no matter how fast I click the submit button.
Is there a reliable way to prevent duplicate database inserts from occurring with the first code snippet so that it behaves just like the second snippet?
Generally a client side onsubmit handler can help stop accidental rapid-submissions, but I usually opt for a server side solution using _SESSION variables. Just create a hash of the $_POST array, store it as a session variable, and then compare the next submitted _POST to the hashed version:
session_start();
//if this is the first time they've posted, just create a placeholder session var
if (!isset($_SESSION['repost_hash'])){$_SESSION['repost_hash']="";}
//if the post array is a duplicate, nullify the submission
if (isset($_POST) && md5(serialize($_POST)) == $_SESSION['repost_hash']){
unset($_POST);
} else { //otherwise, if this is new data, update the stored hash
$_SESSION['repost_hash'] = md5(serialize($_POST));
}
...
If you only want to stop repeat-submissions for a short period of time, you can also have a second $_SESSION variable store the time of the last hash, so you can expire it after a second or two.

How to reserve a MySQL row and update

I was asked for a feature that I'm not sure how to accomplish. Have a form page where a phone number is entered - I need to get that phone number into a row on a button submit, and in the same form about 5-10 minutes later fill out the rest of the options and update that same row. Not sure how to do that. I was thinking to use mysql_insert_id() / last_insert_id()..
">
<?php
$hostname = "*";
$username = "*";
$password = "*";
$EmpID = $_COOKIE["dsmkttrackerid"];
mysql_connect($hostname, $username, $password) or die(mysql_error());
mysql_select_db("*");
$sql="SELECT * FROM User WHERE EmpID='".$EmpID."'";
$r = mysql_query($sql);
if(mysql_affected_rows()==0){
}
else {
$row = mysql_fetch_array($r);
$NT_Login = $row['NT_Login'];
$Job = $row['Job'];
if ($Job == "MADMIN" || $Job == "ADMIN" || $Job == "MPLT") {
$Pilot=true;
}
}
date_default_timezone_set('America/Chicago');
$date = date("Y-m-d",time());
$time = date("H:i:s",time());
if ($_POST[Comment]) {
$_POST[Comment] = mysql_real_escape_string($_POST[Comment]);
$PHONE = mysql_real_escape_string($_POST[PHONE]);
//check if dup post, caused by refreshing page.
$sql="Select * from Data where Comment='$_POST[Comment]'";
if (mysql_num_rows(mysql_query($sql)) <1) {
$sql="INSERT INTO Data(`Date`, `Time`, `EmpID`, `PHONE`, `Comment`,
`OrigMention`, `OrigSent`, `PostMention`, `Mod`, `Pilot`)
VALUES('$date','$time','$_POST[EmpID]','$_POST[PHONE]','$_POST[Comment]',
'$_POST[OrigMention]','$_POST[OrigSent]','$_POST[PostMention]',
'$_POST[Mod]','$_POST[Pilot]')";
if (!mysql_query($sql))
{
die('Please report this error to your supervisor: <br />'
. mysql_error());
}
else {
$Msg = "Post #".mysql_insert_id()." Tracked Successfully.";
}
}
else {
//if dup is found:
$Msg ="Duplicate Entry Detected.";
}
}
else {
//if no post was sent to the server:
$Msg ="";
}
?>
Get the ID using mysql_insert_id() as you've done. Store it in the session, and you can reference it for the rest of the form.
As tandu said, you can use mysql_insert_id() to get the id, then store it in the session, and reference it later. I caution, however, if you use the default PHP sessions (via session_start() and $_SESSION and the like), the session will not exist forever, so it's possible, if they leave their computer and come back a few hours later (eg, coming back from lunch), the would have to start the process all over.
I think it's best to have your own session handlers (in my personal experience, this has proven to be more controllable, and secure anyway). Another option, however, is to send the id back to the user as a hidden value in the form, such as:
<form ...>
<input name="dbid" value="<?=$id?>" type="hidden" />
<!-- the rest of the form.... -->
If you don't want to reveal the dbid to a potential malicious user, then this is out of the question. And, strictly speaking, I think this is bad practice. However, it is another option that works.
EDIT: Yet another option is to use cookies

Categories