stymiee

Handling Online Payments Part 7 - Preventing Automated Form Submissions

by All Star ‎04-06-2011 07:32 AM - edited ‎10-20-2011 09:57 AM (10,696 Views)

This is part seven of a multi-part series on handling online payments.

 

In Part 1 of this series we identified our goals (creating a payment form that was usable, accessible, and secure) and began by creating the form we will use to capture payment information. In Part 2 of this series we continued this process by exploring how we will handle the data submitted by that form. In Part 3 of this series we took the data received and sanitized in Part 2 and validated that it was in a format we required. In Part 4 of this series we took the errors we found in Part 3 and displayed them in a user-friendly format to minimize cart abandonment. In Part 5 of this series we processed the payment and handled the response returned to us whether it be approved, declined, or an error completing the transaction process.

 

In Part 6 of this series we changed our focus to improving upon our form to make it more user-friendly, secure, and easier to maintain. We accomplished this by preventing duplicate form submissions using the POST/REDIRECT/GET design pattern. We will continue along this path and see how we can prevent spambots and other malware from making automated form submissions.

 

The Problem

 

Any webmaster who has run a website for long enough will know about the frustration of dealing with spambots submitting forms on their website. This can cause problems with unwanted data, spam, and even sophistication attacks that can compromise your website. In our case it can result in unwanted payments being made possibly with stolen credit cards. The potential chargebacks alone could put a company out of business.

The Solutions

 

Fortunately as web developers we have options for preventing these submissions from occurring. "Prevention" being the key word as we want to stop the submissions before payment can be made. There are several methods we can implement to accomplish this for us and we will focus on using two of them. They are:

 

  • Honeypot
  • Form Token

 

Honeypot

 

According to Wikipedia "in computer terminology, a honeypot is a trap set to detect, deflect, or in some manner counteract attempts at unauthorized use of information systems." For our form, the honeypot will be a fake form field that only bots can see. The reason for this is automated bots will often attempt to complete every form field they can find in the HTML of your form. This includes fields that are hidden from view from normal users. So if this field has a value when the form is submitted we can assume it was submitted by a bot and produce and error message. Bots will not be able to understand or respond to the error and will be prevented from succeeding.

 

Let's see how we would do this in our form. We will start by adding the hidden form field.

 

<form action="/payment-form.php" method="post">
    <p class="hp">
        <input type="text" name="ssn" id="ssn" value="">
    </p>

 

The HTML for this is pretty straight forward. The two things that should be noted are the class we gave the <p> tag and the name of the honeypot form field. We gave the <p> tag a class name as we will use CSS to hide this form field from users. We will use CSS instead of JavaScript since a significant number of users turn off JavaScript for security purposes and may be exposed to this supposed-to-be invisible form. CSS, on the other hand, is rarely, if ever, turned off by users so we can reliably count on this field being hidden from all of our users. We named the field "ssn" because it is the acronym for Social Security Number. Automated bots are preprogrammed to look for common field names and fill them will legitimate looking data. Social security numbers are a field they would certainly know how to fill in so we can count on it being completed by an automated bot if it finds this field.

 

    Now that we have our form field in place we need to hide it with CSS. That's very easy to do with this code:

 

/* hp stands for HoneyPot */
.hp
{
    display: none !important;
}

 

This CSS will hide our form field for us. We use !important to make sure that this rule takes precedence over any other rule that may accidentally display it to the user.

 

Our last step is to add a check in our PHP code to look for this form field and, if there is a value submitted, generate an error.

 

$honeypot = sanitize($_POST['ssn']);

if (!empty($honeypot))
{
    $errors['hp'] = "This form submission is invalid. Please try again or contact support for additional assistance.";
}

 

In this code we read in the value of the honeypot field and check to see if it has a value. If it does we generate an error. We make sure the error is human friendly just in case a user somehow manages to submit a value for this field by accident. But if a bot sees it they'll be stick and their attempt to abuse our form will be thwarted.

 

Form Token

 

A form token provides protection against forms of attacks against your site by placing a unique random token as a hidden field that can only be used once. If the form is submitted without a token, or the wrong token, an error is generated. For this to work we need to place the token in the form as a hidden field and then store its value on the server so we know what value to expect when the form is submitted. We will start by looking at the code that generates the token for us. We will place it after our PHP code for handling form submissions but before our HTML.

 

$_SESSION['token'] =  md5(uniqid(rand(), true));

 

Generating the token is easy to do as we take a random string and hash it using the MD5 algorithm. The result will be a random 32 character string. We place it in a $_SESSION variable so its value is stored across page requests and we can access it after the form is submitted.

Now we need to place this token in our form as a hidden field so users won't see it but it will be included in their form submission.

 

<form action="/payment-form.php" method="post">
    <input type="hidden" name="token" id="token" value="</php echo $_SESSION['token']; ?>">

 

Now that we have the token in place we need to have our PHP code verify the correct token was submitted.

$token = sanitize($_POST['token']);

if ($token !== $_SESSION['token'])
{
    $errors['token'] = "This form submission is invalid. Please try again or contact support for additional assistance.";
}

 

An additional advantage of the form token is it also protects against Cross Site Request Forgery attacks (CSRF). These attacks are when a user attempts to mimic a legitimate user by hijacking their browsing session and submit the form as that user. Using form tokens makes it extremely difficult for the attacker to submit the form with the proper token making their chances for success very, very small. (I suggest reading more on CSRF attacks as they are a legitimate danger for any website that accepts credit card information).

 

Updated Payment Form

 

To see our new form with the honeypot and form token included just download the attachment at the end of this blog post.

 

What's Next?

 

Now that we have enhanced our payment form with user-friendly error messages and made it more difficult for spambots and other malware from making automated submissions, we can focus on making usability improvements with JavaScript and Cascading Style Sheets. Our next installment will show examples for both.

 

The Handling Online Payments Series

 

  1. Part 1 - Basic Information and Our Form
  2. Part 2 - Reading In And Sanitizing Submitted Data
  3. Part 3 - Data Validation
  4. Part 4 - Handling Validation Errors
  5. Part 5 - Processing Payment and Handling the Response
  6. Part 6 - Preventing Duplicate Submissions with POST/REDIRECT/GET
  7. Part 7 - Preventing Automated Form Submissions
  8. Part 8 - Using JavaScript To Increase Usability
  9. Part 9 - HTML and CSS Enhancements
  10. Part 10 - A Little Bit More PHP
---------------------------------------------------------------------------------------------------


John Conde is a certified Authorize.Net developer

Comments
by JMW on ‎04-14-2011 07:52 PM

Learning a lot from this so far.

I have a question.

Is it possible to capture input from previous pages in a multi-page form and populate fields in this payment form if this form is the last page?

Would that be a violation of the PCI compliance regs?

If so and it's not a violation, could you post an example of the code please....

by All Star on ‎04-15-2011 07:15 AM

You are derfinitely not required to use a one page form to process payments. It is just done this way in this series to keep it simple and allow us to focus on core principles. You can have a multi-page checkout and store information in a $_SESSION and carry it over from page to page if you'd like. If it is not credit card information (e.g. billing or shipping information) then PCI compliance won't be an issue. If it is credit card information then PCI compliance becomes an issue for sure even though the storage is only temporary.

by JMW ‎04-18-2011 01:05 PM - edited ‎04-18-2011 01:19 PM

Could this form be used for the Direct Post Method or SIM?

If so what changes if any need to be made?

by All Star on ‎04-18-2011 01:45 PM

This form only works for AIM since DPM and SIM both post directly to Authorize.Net and bypasses your website altogether. It is possible to alter this code to work with SIM, though, if you then use JavaScript to submit the processed form. There are examples of this in the forums.

by runinord on ‎06-10-2011 07:23 PM

Please correct the following:

<form action="/payment-form.php" method="post">
    <input type="hidden" name="token" id="token" value="</php echo $_SESSION['token']; ?>">

 

To:

<form action="/payment-form.php" method="post">
    <input type="hidden" name="token" id="token" value="<?php echo $_SESSION['token']; ?>">

 

I.e. change </php echo  too <?php echo or this will not work.

 

Cheers,

Runi

by runinord on ‎06-10-2011 07:45 PM

In additin the following also needs to be changed to work:

Change

if ($token !== $_SESSION['token'])
{
    $errors['token'] = "This form submission is invalid. Please try again or contact support for additional assistance.";
}

 

To:

 

   if(!strcasecmp($token,$_SESSION['token'])===0){
    $errors['token'] = "This form submission is invalid. Please try again or contact support for additional assistance.";
}

Great tutorial guys.

Runi

by runinord on ‎06-10-2011 08:08 PM

In addition, I could not get the sugested css to work, it displaied a box that is visible. So what I did was to replace :

 

<form action="/payment-form.php" method="post">
    <p class="hp">
        <input type="text" name="ssn" id="ssn" value="">
    </p>

 

With

 

<form action="/payment-form.php" method="post">
<div style="display:none;">
<input type="text" name="ssn" id="ssn" value="">
</div>

Which works as intended and then you can remove the .hp css from you css file too.

Cheers,

Runi

About the Author
  • Authorize.Net Developer Community Manager
Announcements
Labels