Solved

Browser's Back Button is robbing us!

Posted on 2014-10-04
20
74 Views
Last Modified: 2016-05-07
Hi Guys,
I have a real problem and it may not be an easy one to solve.
I hope I can explain this clearly.

I have a number of PHP pages which perform the following tasks. They are all within an iFrame.
Step 1) Page_1.php Takes the customers payment then presents Page_2.php.
Step 2) Page_2.php Provides a Paid Receipt and a link to Page_3.php
Step 3) Page_3.php Presents the customer with the merchandise then sends the customer to Page_4.php
Step 4) Page_4.php Summarizes the sale & provides delivery information then refreshes to the LogIn.php page.

Now here's the problem.
If the customer is on Page_4.php or even after it refreshes to LogIn.php, and clicks the Browser's BACK Button (not to be confused with a javascript history() button), the browser (and it does not matter which browser) sends the customer back to Page_3.php with all of its $variables intact.
That is a BIG TIME DISASTER!
Because on Page_3.php the customer then can get the merchandise again without paying for it the second time!
This allows the customer to get/steal FREE merchandise each time the customer clicks the Browser's BACK Button. The customer can do this all day long!

How can this be prevented?
I know this is a tough challenge to solve, but I'd appreciate all the help I can get on this.
Sas
0
Comment
Question by:sasnaktiv
  • 6
  • 5
  • 3
  • +2
20 Comments
 
LVL 82

Expert Comment

by:Dave Baldwin
Comment Utility
Tthe Browser's BACK Button and the javascript history() are exactly the same thing,  There is Only one history list.  When go 'back' by either method, the browser displays the last image of the previous page without going back to the server... unless the page was loaded thru a POST method.  Then it will check with the server.  

In the shopping carts I maintain, I use PHP Sessions and on the last page, I delete the session after the purchase has been completed.  Also all the pages use POST to go to the next page.  Then if someone clicks on 'Back' on the last, the browser reloads the prevous page except that since the session data is gone, the order data is no longer there.
0
 
LVL 82

Expert Comment

by:leakim971
Comment Utility
on page3 use
location.replace("/path/to/page4.html");

more info on this page (see You can't change history section an d others):
http://www.hunlock.com/blogs/Mastering_The_Back_Button_With_Javascript

but look like your application have bad design, you should not trust any operation logic on the client side
0
 
LVL 47

Expert Comment

by:dlethe
Comment Utility
You need to change the design.  A quick fix is to simply open up a new browser instance without the address bar and back button.   The better solution is to incorporate a unique Id in every page that can only be used once

Lots of solutions online to prevent back buttons as well as page refreshes from having undesired results
0
 
LVL 1

Author Comment

by:sasnaktiv
Comment Utility
Okay Dave,
That makes sense but I must pass the data as a "GET" method as follows:
<meta http-equiv='refresh' content='0;url=Page_3.php?LotsOfVariables=$LotsOfVariables' method='GET' />

So how can I use  the POST method and the <meta 'refresh' content=0
???

I've been able to pass the data using the  "location.replace("/path/to/page4.php??LotsOfVariables=$LotsOfVariables");"  suggested by "leakim971" but the Browser's Back Button still brings me to Page_3.php

Sas
Sas
0
 
LVL 82

Expert Comment

by:Dave Baldwin
Comment Utility
You can NOT use POST with <meta 'refresh'.  All simple links are GETs.  If you are stuck with this poor design, @dlethe's suggestion of a unique id is the way to go.  You can also use Sessions with GET methods but POST is always going to be better because it can not be edited in the address bar.

Virtually all decent shopping carts use Sessions.  http://us1.php.net/manual/en/book.session.php
0
 
LVL 108

Expert Comment

by:Ray Paseur
Comment Utility
Please see: http://iconoun.com/demo/prevent_multi_submit.php
<?php // prevent_multi_submit.php
error_reporting(E_ALL);

/**
 * PREVENT REPEATED DATA SUBMISSIONS DUE TO BROWSER REFRESH
 * RE-CLICK ON SUBMIT, OR BROWSER BACK BUTTON
 *
 * GET-METHOD REQUESTS SHOULD BE IDEMPOTENT AND (IN THEORY)
 * MUST NOT DISRUPT THE DATA MODEL.  THIS FUNCTION CAN TEST
 * EITHER $_GET OR $_POST REQUEST VARIABLES
 *
 * EXAMPLE:
 *    if ( multi_submit() )
 *    {
 *       handle error
 *    }
 *    else
 *    {
 *       normal processing
 *    }
 */


// ALWAYS START THE PHP SESSION ON EVERY PAGE
session_start();


// A FUNCTION TO RETURN TRUE OR FALSE ABOUT MULTI-SUBMIT CONDITIONS
function multi_submit($type="POST")
{
    // MAKE THE FUNCTION WORK FOR EITHER GET OR POST SUBMITS
    $input_array = (strtoupper($type) == "GET") ? $_GET : $_POST;

    // GATHER THE CONTENTS OF THE SUBMITTED FIELDS AND MAKE A MESSAGE DIGEST
    $string = NULL;
    foreach ($input_array as $val)
    {
        $string .= $val;
    }
    $string = md5($string);

    // IF THE SESSION VARIABLE IS NOT SET THIS IS NOT A MULTI-SUBMIT
    if (!isset($_SESSION["multi_submit"]))
    {
        $_SESSION['multi_submit'] = $string;
        return FALSE;
    }

    // IF THE SESSION DATA MATCHES THE MESSAGE DIGEST THIS IS A MULTI-SUBMIT
    if ($_SESSION['multi_submit'] == $string)
    {
        return TRUE;
    }
    else
    {
        $_SESSION['multi_submit'] = $string;
        return FALSE;
    }
}


// SHOW HOW TO USE THE FUNCTION
if (!empty($_POST))
{
    if (multi_submit())
    {
        echo "ALREADY GOT THAT";
    }
}


// CREATE THE FORM FOR THE DEMONSTRATION
$form = <<<FORM
<form method="post">
ENTER SOMETHING, THEN REENTER IT
<input name="mydata" />
<input type="submit" />
</form>
FORM;

echo $form;

Open in new window

Please let me know if you have any questions.
0
 
LVL 1

Author Comment

by:sasnaktiv
Comment Utility
No luck so far Guys.
Based on Mr. Baldwin's input I've rewritten much of the code to use POST instead of the GET method, but the Browser's Back Button still brings back Page_3.php with all its values intact.
I'm trying to make Ray's invention work in my environment but having a bit of a time doing so. For one thing I'm not so sure it should be added to Page_3.php. I need to kick this around some more.

Maybe there's a way on Page_3.php to detect if the customer used the Browser's Back Button to get to it.
And if so, retrieve a $variable which I can use to disable/enable the SUBMIT button that delivers the merchandise.

Any ideas on those lines?
Sas
0
 
LVL 108

Expert Comment

by:Ray Paseur
Comment Utility
I can't dwell on this too much tonight, but there is no need to load all those different pages.  A single PHP script can create several different HTML documents, depending on the contents of the request and the existing values in the database.  You may want to think about redesigning the application with that in mind.
0
 
LVL 82

Expert Comment

by:Dave Baldwin
Comment Utility
Maybe there's a way on Page_3.php to detect if the customer used the Browser's Back Button to get to it.
No, there isn't.  The 'Back' function is strictly in the browser.  It reloads the last image of the previous page from cache if it is available.  If Page_3.php was loaded thru POST, then you should at least get the message asking if you really want to do that.
0
Highfive Gives IT Their Time Back

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 
LVL 82

Expert Comment

by:Dave Baldwin
Comment Utility
I'll see if I have time to come up with a demo that works.  Maybe tomorrow.
0
 
LVL 82

Assisted Solution

by:Dave Baldwin
Dave Baldwin earned 166 total points
Comment Utility
Although I have been maintaining shopping carts for some time, I never had to make this work by myself before.  I'll give the simple demo scripts in order but they are probably far different than what you are currently doing.  

The keys to it are the initial POST goes to an 'invisible' page which stores the values in $_SESSION variables and then redirects to the second 'visible' page.  This is done to make it impossible to use the Back button to repost the values.  The second page reads the data from the $_SESSION variables and displays them.  When you click on the link to the last page, the last page reads the $_SESSION variables again and then destroys the session so you can't access the data again.  When you click on back at that point, the data no longer displays on the previous page.

PostForm.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
 "http://www.w3.org/TR/html4/loose.dtd">

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>POST Form</title>
</head>
<body>
<h1>POST Form</h1>

<form action="addvalue.php" method="post">
<input type="text" name="input01" value="Input01" /><br>
<input type="text" name="input02" value="Input02" /><br>
<input type="submit" name="submit" value="Submit" />
</form>

</body>
</html>

Open in new window


addvalue.php
<?php
error_reporting(E_ALL);
ini_set('display_errors','1');
session_start(); 

# settings of vars
if (!isset($_POST['input01']))  $input01 = ''; else $input01 = $_POST['input01'];
if (!isset($_POST['input02']))  $input02 = ''; else $input02 = $_POST['input02'];

$_SESSION['input01'] = $input01;
$_SESSION['input02'] = $input02;

header('Location: postmid.php');
exit;

?>

Open in new window


postmid.php
<?php
error_reporting(E_ALL);
ini_set('display_errors','1');
session_start(); 

# settings of vars
if (!isset($_SESSION['input01']))  $input01 = ''; else $input01 = $_SESSION['input01'];
if (!isset($_SESSION['input02']))  $input02 = ''; else $input02 = $_SESSION['input02'];

?>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
 "http://www.w3.org/TR/html4/loose.dtd">

<html>
<head>
<title>postmid.php</title>
</head>
<body>
<h1>postmid.php</h1>
<input type="text" name="input01" value="<?php echo $input01; ?>" /><br>
<input type="text" name="input02" value="<?php echo $input02; ?>" /><br>

<a href="postend.php">Go to postend.php</a>
</body>
</html>

Open in new window


postend.php
<?php
error_reporting(E_ALL);
// 
// get and kill the old session ID
session_start(); 

//get the data we want
if (!isset($_SESSION['input01']))  $input01 = ''; else $input01 = $_SESSION['input01'];
if (!isset($_SESSION['input02']))  $input02 = ''; else $input02 = $_SESSION['input02'];

// Unset all of the session variables.
$_SESSION = array();

// If it's desired to kill the session, also delete the session cookie.
// Note: This will destroy the session, and not just the session data!
if (ini_get("session.use_cookies")) {
    $params = session_get_cookie_params();
    setcookie(session_name(), '', time() - 42000,
        $params["path"], $params["domain"],
        $params["secure"], $params["httponly"]
    );
}

// Finally, destroy the session.
session_destroy();

?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
 "http://www.w3.org/TR/html4/loose.dtd">

<html>
<head>
<title>Untitled</title>
</head>
<body>
<h1>postend.php</h1>
<input type="text" name="input01" value="<?php echo $input01; ?>" /><br>
<input type="text" name="input02" value="<?php echo $input02; ?>" /><br>

</body>
</html>

Open in new window

0
 
LVL 108

Assisted Solution

by:Ray Paseur
Ray Paseur earned 166 total points
Comment Utility
Sas, basically there are two kinds of requests.  To understand them, you would need to know the information in this article.
http://www.experts-exchange.com/Web_Development/Web_Languages-Standards/A_11271-Understanding-Client-Server-Protocols-and-Web-Applications.html

There are idempotent requests and mutative requests.  Idempotent requests can be made via GET.  Examples include a Google search, a web page view, etc.  You can make these kinds of requests over and over - from a browser or from an automated script (or even an attack 'bot) and nothing ever changes.  Mutative requests must not be made via GET since they can change the data model.  Examples include purchasing an item, transferring funds, etc.  Each such request requires an affirmative signal of agreement from the client.  Think of the way the ATM works -- you have to give it your PIN number for each transaction.

With this in mind, go back and look at your pages 1-4.  Which are idempotent and which are mutative?  Are there any that are both kinds of pages at once?  When you get to the answer to these questions you will understand why I suggest that you may want to redesign the entire application.  As described, this is potentially dangerous and unworkable (and I haven't even thought about the iFrame, which is another potential point of confusion). Your design must allow any request to any page at any time in any order without unwanted changes to the data model.  You simply cannot assume that there will be a "flow" from page to page.  You must design a system that is immune to random requests.

There are really good designs for online commerce in the PayPal developers documentation.  This might be a good place to start your learning adventures.
https://devblog.paypal.com/
0
 
LVL 47

Expert Comment

by:dlethe
Comment Utility
As I stated earlier, two tried and true methods are to launch a new window (presumably after they log in), where this new window removes the buttons (the refresh can get you as well, along with code that can access the history).  Even seeing the address bar can be used for an exploit.  The other is to incorporate a USE-ONCE ID field , but that is more work.

I urge you to just prevent the problem by opening up a new session where you control 100% of the environment.  You're talking only a few minutes and problem solved.

P.S. you ARE using SSL, right???
0
 
LVL 1

Author Comment

by:sasnaktiv
Comment Utility
Hi Guys,
First of all I want to thank you all for the attention and input.

I've been kicking around everyone's suggestion that I could understand.

My last one was to apply Dave Baldwin's 4 files.
Here's what happens:
1) PostForm.html begins the input and submits it
2) addvalue.php captures the $variables and sends them to
3) postmid.php displays the $variables and presents a SUBMIT button aimed at
4) postend.php which merely displays the $variables
That's all well & good.
When I click the Browser's Back Button I am returned to postmid.php which nolonger has the values.
That's fabulous!
But when I click the Browser's Back Button again ----- I find myself with PostForm.html and all of the $variables still intact, and ready to be SUBMITTED again.
That's not so fabulous!

Is there a way to disable the SUBMIT button in  PostForm.html once it has been clicked?
Thanks,
Sas


Also, dlethe suggests that I open a new session. But I thought I was doing that since every document starts with "session_start();"   Is that not the case?
0
 
LVL 82

Expert Comment

by:Dave Baldwin
Comment Utility
"session_start();" only starts a new session if there is no current session.  It continues the current session if one exists.  It is necessary to use "session_start();" on each page to give you access to the $_SESSION variables.

The example I gave you destroys the current session on the last page so the $_SESSION variables are no longer there.  When you click on Back, the previous page starts a new session since at that point there is no current session.  Going back a second time has never been a problem so I didn't give it any thought.
0
 
LVL 1

Accepted Solution

by:
sasnaktiv earned 2 total points
Comment Utility
I'VE GOT IT!!!
Yes I'm shouting.
I've come up with a simple solution that goes in a totally different direction from the route we've all been taking.

I have found a way on Page_3.php to detect if the customer used the Browser's Back Button to get back to Page_3.php.

Then I immediately direct the customer to yet another page completely. The "MainMenu.php" page would be ideal.

Once in MainMenu.php the customer can't use the Browser's Back Button because I've installed the identical code in the MainMenu.php which targets itself. So when the customer  uses the Browser's Back Button again and again and again the MainMenu.php reloads itself each time.

The only way off the MainMenu.php page is to use the menu buttons legitimately.

Thanks for the help everyone,
Sas aka MacGyver
0
 
LVL 47

Assisted Solution

by:dlethe
dlethe earned 166 total points
Comment Utility
That test can be circumvented with any HTML5 compliant browser with a few lines of JavaScript.  Since this is a commerce site then I advise against it..

Why not just do the quick fix by launching a NEW window after the login that has no address bar and no buttons?
0
 
LVL 1

Author Comment

by:sasnaktiv
Comment Utility
Why not just do the quick fix by launching a NEW window after the login that has no address bar and no buttons?

Thanks Dlethe,
Nice approach, but how do I that?
Sas
0

Featured Post

Highfive Gives IT Their Time Back

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Join & Write a Comment

Introduction A frequently asked question goes something like this:  "I am running a long process in the background and I want to alert my client when the process finishes.  How can I send a message to the browser?"  Unfortunately, the short answer …
This article discusses four methods for overlaying images in a container on a web page
The viewer will learn how to create and use a small PHP class to apply a watermark to an image. This video shows the viewer the setup for the PHP watermark as well as important coding language. Continue to Part 2 to learn the core code used in creat…
This tutorial will teach you the core code needed to finalize the addition of a watermark to your image. The viewer will use a small PHP class to learn and create a watermark.

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

6 Experts available now in Live!

Get 1:1 Help Now