12.2. Basic Spam Prevention

Unfortunately, any web form is a potential target for spammers, both human and automated. If left unchecked, spammers can ruin a blog by flooding the comments with irrelevant links and other garbage. The spam buries legitimate comments and makes it difficult to find anything meaningful in the comment section, which can cause your users to give up on leaving or reading comments altogether.

To address this problem, you'll need to take some anti-spam measures on your comment form. There are myriad options available for spam prevention, ranging from user-dependent systems such as CAPTCHA[] (the squiggly letters and numbers you've no doubt seen on some web forms), to server-side technologies such as Akismet[] (which identifies spam pretty well without requiring the user to do anything).

[] http://en.wikipedia.org/wiki/CAPTCHA

[] http://akismet.com/

In the spirit of learning, let's walk through how to build a custom form to curb spam, sometimes referred to as challenge-response. In this case, you present your user with a simple math question that must be answered to confirm that he isn't a spam bot.

12.2.1. Creating a Basic Logic Question

It's not foolproof, but adding a basic logic question to your comment forms is a great way to trip up less sophisticated spam bots, without asking much of your users—it doesn't require too much effort to answer the question: "What is 2 + 3?"

Unfortunately, some spam bots are wily enough to parse the logic question and determine the answer. To confuse these bots, you can obfuscate the values by converting them to their HTML entities. Again, this isn't a perfect system, but it can prevent spam on a small site fairly well.

12.2.1.1. Generating Random Numbers

The first task is to generate a math question. To keep things simple, all answers to the math question should be less than 10. Keeping the question simple ensures that your users won't even have to think before answering the question, which is an important consideration: you don't want to make it any more difficult to post a comment than is absolutely necessary.

To keep things random and simple, you can create an array of two numbers between 1 and 4 using PHP's built-in mt_rand() function, which generates random numbers based on a low and high limit, then passes these results as arguments. You store the sum of the two numbers in a new session variable called $_SESSION['challenge'], and you use this variable to verify that the user supplied the proper response. You place the logic to challenge your user in a new private method called generateChallenge(), which you add to comments.inc.php:

private function generateChallenge()
    {
        // Store two random numbers in an array
        $numbers = array(mt_rand(1,4), mt_rand(1,4));

        // Store the correct answer in a session
        $_SESSION['challenge'] = $numbers[0] + $numbers[1];
    }

NOTE

PHP also has a function called rand() that works similarly to mt_rand(). It has been found that the results from rand() in some cases aren't actually very random, and that the results from mt_rand() tend to be more reliable when randomness is important.

12.2.1.2. Obfuscating the Values

So far you have two random numbers in an array; next, you need to obfuscate the values. Obfuscation in programming[] is the practice of making code difficult to interpret. This is generally done as a security measure. You want to obfuscate the values on the current form to make it more difficult for a spam bot to parse your form and answer the challenge question.

[] http://en.wikipedia.org/wiki/Obfuscated_code

Converting the random numbers to their ASCII[] character code equivalents should help you baffle spam bots. ASCII stands for American Standard Code for Information Interchange, and it bridges the gap between humans and computers. Computers can understand only numbers—every symbol is mapped to a numeric code. For example, the ASCII equivalent of a questions mark (?) is 63.

[] http://en.wikipedia.org/wiki/ASCII

PHP provides a function to perform this conversion called ord(). To convert both numbers in the array $numbers, you use the array_map() function, which enables you to apply a function to each value in an array individually.

Obfuscate the numbers by adding the following code in bold to generateChallenge():

private function generateChallenge()
    {
        // Store two random numbers in an array
        $numbers = array(mt_rand(1,4), mt_rand(1,4));

        // Store the correct answer in a session
        $_SESSION['challenge'] = $numbers[0] + $numbers[1];

        // Convert the numbers to their ASCII codes
        $converted = array_map('ord', $numbers);
    }

12.2.1.3. Adding the Math Question to the Form

Now that you have two obfuscated numbers, it's time to generate the HTML markup that displays the challenge question to the user.

The markup creates a text input field in which the user will type his answer to the challenge question. You label the text input with an obfuscated string that (you hope) will confuse a spam bot when it displays this question: "What is n + m?" n is the first random number, while m is the second.

Add the code in bold to generateChallenge() to return the markup for a challenge question:

private function generateChallenge()
    {
        // Store two random numbers in an array
        $numbers = array(mt_rand(1,4), mt_rand(1,4));

        // Store the correct answer in a session
        $_SESSION['challenge'] = $numbers[0] + $numbers[1];

        // Convert the numbers to their ASCII codes
        $converted = array_map('ord', $numbers);

// Generate a math question as HTML markup
        return "

        <label>&#87;&#104;&#97;&#116;&#32;&#105;&#115;&#32;
            &#$converted[0];&#32;&#43;&#32;&#$converted[1];&#63;
            <input type="text" name="s_q" />
        </label>";
    }

12.2.2. Adding the Challenge Question to the Form

At this point, you're ready to add the challenge question to the comment form. To do this, you need to modify showCommentForm() by creating a new variable, $challenge, to store the output of generateChallenge(), and then insert it into the form's HTML markup.

You add the challenge question by incorporating the code in bold to showCommentForm():

// Display a form for users to enter new comments with
    public function showCommentForm($blog_id)
    {
        $errors = array(
            1 => '<p class="error">Something went wrong while '
                . 'saving your comment. Please try again!</p>',
            2 => '<p class="error">Please provide a valid '
                . 'email address!</p>'
        );
        if(isset($_SESSION['error']))
        {
            $error = $errors[$_SESSION['error']];
        }
        else
        {
            $error = NULL;
        }

        // Check if session variables exist
        $n = isset($_SESSION['n']) ? $_SESSION['n'] : NULL;
        $e = isset($_SESSION['e']) ? $_SESSION['e'] : NULL;
        $c = isset($_SESSION['c']) ? $_SESSION['c'] : NULL;

// Generate a challenge question
        $challenge = $this->generateChallenge();

        return <<<FORM
<form action="/simple_blog/inc/update.inc.php"
    method="post" id="comment-form">
    <fieldset>
        <legend>Post a Comment</legend>$error
        <label>Name
            <input type="text" name="name" maxlength="75"
                value="$n" />
        </label>
        <label>Email
            <input type="text" name="email" maxlength="150"
                value="$e" />
        </label>
        <label>Comment
            <textarea rows="10" cols="45"
                name="comment">$c</textarea>
        </label>$challenge
        <input type="hidden" name="blog_id" value="$blog_id" />
        <input type="submit" name="submit" value="Post Comment" />
        <input type="submit" name="submit" value="Cancel" />
    </fieldset>
</form>
FORM;
    }

You can test this code by loading an entry in your browser. Just below the comment text area, you can see a new input labeled with the challenge question (see Figure 12-2).

Figure 12.2. The challenge question displayed on the contact form

12.2.3. Verifying the Correct Answer

You've presented the user is being presented with a challenge question. The next step is to add a private method that compares the user's response to the challenge with the correct answer.

Call this method verifyResponse(). This method accepts the user's response to the challenge question generated in generateChallenge(), then it compares the value stored in $_SESSION['challenge'] with the supplied response. If they match, the method returns TRUE; otherwise, it returns FALSE.

To avoid potential double-posting or other unexpected behavior, you should store the value of $_SESSION['challenge'] in a new variable ($val), then destroy it using unset().

Add the following method to comments.inc.php:

private function verifyResponse($resp)
    {
        // Grab the session value and destroy it
        $val = $_SESSION['challenge'];
        unset($_SESSION['challenge']);

        // Returns TRUE if equal, FALSE otherwise
        return $resp==$val;
    }

12.2.3.1. Adding the Verification into saveComment()

Next, you need to modify saveComment() so it calls verifyResponse() before saving the comment. This requires passing the proper information from the posted form, checking the method's return value, and storing an error code if the response doesn't match.

This error code doesn't fall within the existing two error codes, so you must define a new error code. For the sake of simplicity, make 3 your new error code for an incorrect response to the challenge question.

You can make these changes by adding the code in bold to saveComment():

// Save comments to the database
    public function saveComment($p)
    {
        // Save the comment information in a session
        $_SESSION['n'] = $p['name'];
        $_SESSION['e'] = $p['email'];
        $_SESSION['c'] = $p['comment'];

        // Make sure the email address is valid first
        if($this->validateEmail($p['email'])===FALSE)
        {
            $_SESSION['error'] = 2;
            return;
        }

        // Make sure the challenge question was properly answered
        if(!$this->verifyResponse($p['s_q'], $p['s_1'], $p['s_2']))
        {
            $_SESSION['error'] = 3;
            return;
        }

        // Sanitize the data and store in variables

$blog_id = htmlentities(strip_tags($p['blog_id']),
            ENT_QUOTES);
        $name = htmlentities(strip_tags($p['name']), ENT_QUOTES);
        $email = htmlentities(strip_tags($p['email']), ENT_QUOTES);
        $comment = htmlentities(strip_tags($p['comment']),
            ENT_QUOTES);

        // Generate an SQL command
        $sql = "INSERT INTO comments (blog_id, name, email, comment)
                VALUES (?, ?, ?, ?)";
        if($stmt = $this->db->prepare($sql))
        {
            // Execute the command, free used memory, and return true
            $stmt->execute(array($blog_id, $name, $email, $comment));
            $stmt->closeCursor();

            // Destroy the comment information to empty the form
            unset($_SESSION['n'], $_SESSION['e'], $_SESSION['c'],
                $_SESSION['error']);
            return;
        }
        else
        {
            // If something went wrong
            $_SESSION['error'] = 1;
            return;
        }
    }

Now the comment form will save a comment only if the user answers the challenge question correctly. The only thing left to do is to add an error message to correspond with error code 3, which you add in showCommentForm() by inserting the code in bold:

// Display a form for users to enter new comments with
    public function showCommentForm($blog_id)
    {
        $errors = array(
            1 => '<p class="error">Something went wrong while '
                . 'saving your comment. Please try again!</p>',
            2 => '<p class="error">Please provide a valid '
                . 'email address!</p>'
            ,
            3 => '<p class="error">Please answer the anti-spam '
                . 'question correctly!</p>'
        );
        if(isset($_SESSION['error']))

{
            $error = $errors[$_SESSION['error']];
        }
        else
        {
            $error = NULL;
        }

        // Check if session variables exist
        $n = isset($_SESSION['n']) ? $_SESSION['n'] : NULL;
        $e = isset($_SESSION['e']) ? $_SESSION['e'] : NULL;
        $c = isset($_SESSION['c']) ? $_SESSION['c'] : NULL;

        // Generate a challenge question
        $challenge = $this->generateChallenge();

        return <<<FORM
<form action="/simple_blog/inc/update.inc.php"
    method="post" id="comment-form">
    <fieldset>
        <legend>Post a Comment</legend>$error
        <label>Name
            <input type="text" name="name" maxlength="75"
                value="$n" />
        </label>
        <label>Email
            <input type="text" name="email" maxlength="150"
                value="$e" />
        </label>
        <label>Comment
            <textarea rows="10" cols="45"
                name="comment">$c</textarea>
        </label>$challenge
        <input type="hidden" name="blog_id" value="$blog_id" />
        <input type="submit" name="submit" value="Post Comment" />
        <input type="submit" name="submit" value="Cancel" />
    </fieldset>
</form>
FORM;
    }

Try the form out by navigating to a blog entry in your browser and posting a comment. If you answer the challenge question incorrectly, the form displays an error message that explains what went wrong (see Figure 12-3).

Figure 12.3. An error is displayed if the challenge question is answered incorrectly

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.198.81