Link to home
Start Free TrialLog in
Avatar of giandem
giandem

asked on

Creating a token to verify a form's origin

I'd like to create a token, as a hidden input, to keep a form 'pure'. I'd like to prevent someone from altering the form. Here's an example:

- user loads page with form
- - before the page is sent to the user
- - a token is created // token = hash(time + salt) perhaps?
- -  token is stored in database (assuming there are no duplicates) with timestamp
- -  token and timestamp are inserted into the form as hidden inputs
- page is loaded

- user submits form
- - token and timestamp are submitted with form data
- - check database for token and timestamp
- - if exists, continue and remove row from database
- - if not exists, fail and start over with new token

What do you think? I'm looking for a best practice, is there a better way?
Avatar of themrrobert
themrrobert
Flag of United States of America image

That still leaves the ability to modify the form and pass the token you are freely handing out.

What is your SPECIFIC scenario?
Avatar of giandem
giandem

ASKER

Sorry, I left out a critical step:

- - The timestamp will be compared to the current time and will reject the token after x amount of time.
Avatar of giandem

ASKER

themrrobert, I don't have a specific scenario but I'd probably check the referer as well, even though I know it's not foolproof.
ASKER CERTIFIED SOLUTION
Avatar of Dave Baldwin
Dave Baldwin
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Place the form behind HTTPS and use CAPTCHA to identify human input.  And accept only known good values.  That's really the right approach.  Trickery like creating form tokens increases complexity for you, but does nothing to dissuade determined attackers.  I would not waste my time on that.  I used to do those things, but found over time that they had no value at all compared to a very simple CAPTCHA.
Avatar of giandem

ASKER

Both of you are making some sense but I'm not yet convinced that you're right. By the way, I want to mention that I'm an amateur at PHP development, not quite a beginner and not yet an expert. Most of my posts here are for my own education and I often can't cite a specific scenario.

DaveBaldwin, the only reason that it's hidden is so it doesn't bother the user. The token would be invalid after the original user submits the form or would expire after a short period of time.

Ray Paseur, isn't a CAPTCHA just a token that bothers the user? Isn't it a similar concept? And, I suppose, complexity is a reasonable price for security.

Assume that the form data is valid and the connection is secure. Also assume that the user is logged-in.
Avatar of giandem

ASKER

I just checked the source here and I wanted to point out that EE uses a token on this form, perhaps for the same reason.
Many developers mistakenly believe that a form token somehow secures the form.  But if the token is stored in a hidden variable, it is no problem at all to use CURL, read the hidden inputs and send them back in the raw POST string.  In addition it's quite easy to make your CURL request look as if it coming from a client browser.  I can show you how to make your request look like it is a Firefox browser referred by Google, but that is beside the point.

Captcha is not really a similar concept at all.  In a captcha test, you store a value on your server and send a puzzle or a picture of the value to the client.  The client must look at the puzzle or picture and type a response.  The goal is to be sure that the client is really a human being.  While it's very easy to regurgitate the contents of a hidden form input, it's programatically much harder to tease out the necessary response to a random inquiry.

Here is a pair of scripts that I have used successfully.  A smart programmer could use a rainbow attack to crack this code in a matter of an hour or two, but that said, it has been adequate for my needs for quite a long time.  The first script generates an image.
<?php // RAY_captcha_image.php
error_reporting(E_ALL ^ E_NOTICE);


// GENERATES A PICTURE OF A NUMBER INTO THE BROWSER OUTPUT


// DECODE THE INCOMING STRING
$data = base64_decode($_GET['dt']);

// CREATE AN IMAGE RESOURCE - CHOOSE THE SIZE THAT BEST MATCHES YOUR PAGE STYLE
$im = imagecreate(46,13);

// WHITE BACKGROUND
$bg = imagecolorallocate($im, 255,255,255);

// GRAY STRIPES
$gray = imagecolorallocate($im, 188,188,188);

// FIREBRICK TEXT
$text = imagecolorallocate($im, 178,34,34);

// ADD THE NUMBER TO THE IMAGE
imagestring($im,5,4,0,$data,$text);

// WRITE A GRAY STRIPE (OR MORE IF YOU CHOOSE)
imageline($im,4,12,38,0,$gray);

// SEND THE IMAGE INTO THE BROWSER OUTPUT STREAM
header('Content-type: image/png');
imagepng($im);
imagedestroy($im);

Open in new window

And the second script demonstrates how this works when inserted into a form.
<?php // RAY_captcha_in_action.php
error_reporting(E_ALL);

// IF ANYTHING WAS POSTED
if (!empty($_POST))
{
    // TEST THE STRINGS
    if ($_POST["_newMd5"] != md5($_POST["_newCode"]))
    {
        // MIGHT WANT TO MAKE THIS USER-FRIENDLY
        echo 'SECURITY CODE NUMBER DID NOT MATCH';
    }
    else
    {
        echo "SUCCESS!";
    }
}
// END OF PHP - PUT UP THE FORM
?>
<form method="post">
<!-- STYLE THIS TO SUIT YOUR PAGE STYLE -->
Type <img style="display:inline;" src="RAY_captcha_image.php?dt=<?php $x = mt_rand(1000,10000); echo base64_encode($x); ?>" /> here:
<input name="_newCode" type="text"   maxlength="64" size="6" autocomplete="off" />
<input name="_newMd5"  type="hidden" value="<?php echo md5($x); ?>" />
<input type="submit" />
</form>

Open in new window

You can test it here.
http://www.laprbass.com/RAY_captcha_in_action.php

You can get much more complicated and annoying captcha images from the Gold Standard, reCaptcha.
http://en.wikipedia.org/wiki/ReCAPTCHA

HTH, ~Ray
Tokens can stop automated resubmission of the same form over and over again. One way is to embed the EXPIRY time and the ip address in the token. For example let us say that you want a form to 'live' for 20 minutes then create an token using MD5, the IP, the expiry and a secret salt string. Then you do this

$expires = time() + 20*60;
$token = md5( $expires . $_SERVER['REMOTE_ADDR'] . "my secret string");

You then bury two hidden fields in the form

<input type='hidden' name='exp' value='<?php echo $expires; ?>' />
<input type='hidden' name='tok' value='<?php echo $token; ?>' />

When the form is submitted you read the expiry time from the form, pick up the IP and regenerate the MD5

$test = md5( $_POST['exp'] . $_SEVER['REMOTE_ADDR'] . "my secret string");

and compare it to the form value. If it passes, then check the expiry time

$submissionOk = false;

if ( $test == $_POST['tok'] )
    if ( is_numeric( $_POST['exp'] )
          if ( intval( $_POST['exp'] ) >= time() )
               $submissionOk = true;

if ( $submissionOk ) {
     ... do stuff - form was valid
}



As the other posters have said, this mechanism has its limits but it does stop links being stored in bookmarks or passed around in email links.


@bportlock: If the link is just to the web page that contains the form, then when I visit that page, I would expect to find a fresh new token and a new expiration time that I could use to send back to the server.  I might be visiting the page as a human being or as a carefully crafted CURL request, but the server-side script would not necessarily be able to tell the difference.  I think you are onto a good design idea if the form creates a GET method request, where the URL exposes the response.  But in the case of a POST response the arguments do not appear in the URL, so the likelihood of passing a POST response in an email link seems small.

What I have used form tokens for, and still do occasionally, is to prevent duplicate submissions of identical data.  But for keeping 'bots out of your forms, I think that nothing beats captcha.

Best to all, over and out, ~Ray
<?php // RAY_multi_submit.php
error_reporting(E_ALL);


// PREVENT MULTIPLE SUBMISSIONS DUE TO REPEATED REFRESH, CLICKS ON SUBMIT BUTTON OR FIRING THE BACK BUTTON
// 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 ALL THE SUBMITTED FIELDS AND MAKE A MESSAGE DIGEST
    $string = NULL;
    foreach ($input_array as $val)
    {
        // CONCATENATE ALL SUBMITTED VALUES
        $string .= $val;
    }
    $string = md5($string);

    // IF THE SESSION VARIABLE IS NOT SET THIS IS NOT A MULTI-SUBMIT
    if (!isset($_SESSION["_multi_submit"]))
    {
        // SAVE THE SUBMITTED DATA MESSAGE DIGEST
        $_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
    {
        // SAVE THE MESSAGE DIGEST TO DETECT FUTURE MULTI-SUBMIT
        $_SESSION['_multi_submit'] = $string;
        return FALSE;
    }
}



// SHOW HOW THIS IS DONE
if (!empty($_POST))
{
    if (multi_submit())
    {
        die("ALREADY GOT THAT");
    }
}



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

echo $form;

Open in new window

I mostly agree with Ray. I work for the company that handles customer support web portals for a lot of big names. If you're a normal consumer, I can all but guarantee that you've used the forms from our product, so form security is a topic that I deal with ALL the time.

Now, our most important forms most often require a person to be logged in before they are even shown, which helps negate a lot of attacks, but every form still has a token attached to help discourage attackers from trying bulk attacks.

A determined attacker COULD write a script that goes out and pulls the page, parses out the variables, and submits the form with the correct token, but that tends to be too time-consuming for most attackers because the page-pulling-and-parsing process slows down the entire attack. It turns the 1000-fire-and-forget-requests-per-second into a fraction of that. Each attack requires a full page load, an HTML parse, and THEN the next submission can occur.

We take it a step further by presenting the token data using Javascript, cookies, and sessions, which is harder for a script to process, since most scripts cannot parse Javascript (but again, a very determined hacker can still write scripts to get around it).

CAPTCHA eliminates all but the most determined of hackers, because CAPTCHA is so difficult for attack scripts to read. I don't think we've ever had any problems with sites that use both CAPTCHA and tokens, and in my experience, CAPTCHA alone is often enough. The key to successful CAPTCHA is -not- do what Ticketmaster does and present you with text so horribly disfigured, it looks like it watched The Ring video last week. I don't like trying to figure out what those letters are - I just want to proceed to the next step already!

If you make it easy enough for users to read (especially if you use actual words), chances are that they will do it without complaining. I see random math problems all the time ("What is 5 plus 2 plus 2?"), which is usually even easier for users and still hard for most scripts to process as long as your problem generation is random enough with its phrasing.

So I'd recommend using CAPTCHA but make it easy on your users. OCR is CPU-intensive enough to discourage bulk attacks, even with easy-to-read images.

Tokens are a nice additional layer, but unless you're protecting credit card numbers, social security numbers, or other PII, I'd just go with CAPTCHA.
We use captchas as well and we wrote a PHP class to generate them, but the form tokens which combine an expiry time combined with an IP address prevent scrapers from analysing the form and then passing it to bot networks.

Every little helps....
Avatar of giandem

ASKER

Ray Paseur, you've almost convinced me. I'm beginning to think that a token is a good idea but not for security.  I do believe that CAPTCHAs are (probably) mandatory for sign-up and log-in forms but probably not a good idea to use when posting in a forum or editing a social network profile.

What would you, and everyone else, consider a good 'blueprint' (minus the CAPTCHA) for securing forms? I'm not asking for code but rather some bullet points or pseudo-code.
The number one thing is to define and accept only good input.  You can use javascript form checking to let the user correct mistakes but you must also provide checking on the page that the form posts to.  Number two is use HTTPS secure connections which requires an SSL/TLS certificate for your website.  If you can require a login before the form, that helps also.

I believe Ray has a page that shows how to escape data that is going to be INSERTed into a database to prevent SQL Injection using mysql_real_escape_string http://us3.php.net/manual/en/function.mysql-real-escape-string.php .  His example pages are very organized, better than mine...

I like a lot of the ideas here and they can all help with different aspects.  For a posting on a forum, you need to make the database doesn't get corrupted so protection from SQL injection is important.  If there is money involved, the security requirements rise.  More security often means more complexity because you put up more obstacles in the way of someone who wants to get in where they don't belong.
1. Your first step should be to make the form as easy-to-use as possible. This means minimizing the number of fields that have to be filled out in order to submit the form, making the form page load as quickly as possible (e.g. don't load a heavy rich HTML editing toolbar component if your users don't make a lot of use of it)

2. Your next step is to secure the form while minimizing impact on the easy experience you've created. A lot of security won't make a difference if nobody wants to fill out the form.

3. Make sure your form-processing script knows what fields to expect. While your form may only have 5 fields to POST, hackers might try to post 10 fields. In some cases, those extra fields may not make any difference, but should there be any future code that tries to process or access the entire POST, you run risks if you're not validating what fields should be processed.

4. Once you know the fields you're expecting, ensure that you're running some kind of data cleansing / sanitizing routine on them. Don't assume that just because the field "someID" should always be an integer, it actually will be one. Force IDs to be integers ($_POST["someID"] = intval($_POST["someID"]) and strip out or html-entitize unwanted characters in POSTed strings to help avoid SQL injection or XSS attacks (especially for forum-type code).

5. Limit your session lengths. Most people don't have a problem logging back into a resource that they want to access, and it limits the opportunity for someone to go steal a cookie somehow and gain access to someone's account.

6. Ask yourself a lot of questions. Most developers just assume things and leave security holes behind as a result. For example, treat any piece of data as if it's a wrapped present. Ask yourself who it's from and ask yourself if you would trust that person to always give you something that is good. Think up questions to ask while you code and then ask yourself those questions. It's a surprisingly good practice, even if it sounds foolish.

7. Use SSL when you can.

8. Keep your web server and PHP engine up-to-date, and make sure your server has a good amount of security (firewall, daily logs, automated IP denial upon attacks, etc...) . All the secure coding in the world won't help you if your system's rear end is exposed.