Skip to content

Instantly share code, notes, and snippets.

@firebus
Created March 30, 2012 15:57
Show Gist options
  • Save firebus/2252420 to your computer and use it in GitHub Desktop.
Save firebus/2252420 to your computer and use it in GitHub Desktop.
NoCaptcha library for PHP forms
<?php
/**
* Provide a set of fields to filter submissions from bots without requiring
* a captcha or otherwise inconveniencing human users
*
* TODO: Be more flexible about which fields are included - by default include
* all but allow caller to specify a subset
*
* TODO: Why you gotta be so static?
*/
class NoCaptchaUtil {
/**
* Generate a set of form inputs for various nocaptcha techniques
*
* @author Russell Uman for Splunk
* @copyright http://creativecommons.org/licenses/by/3.0/
*
* @static
*
* @param string $formId A unique id for this form, probably named after the calling function
*
* @return string
*/
public static function generateNoCaptchaHTML($formId) {
$timestamp = time();
$referer = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
$output = self::generateNoDisplayField();
$output .= self::generateTimestampField($timestamp);
$output .= self::generateRefererField($referer);
$output .= self::generateNonceField($formId, $timestamp, $referer);
return $output;
}
/**
* Validate nocaptcha fields in a $formData array
*
* @static
*
* @param string $formId A unique id for this form, probably named after the calling function
* @param array $submission An array of form data, keyed by field name
* @param int $minTime If form is submitted in less than $minTime seconds, it is invalid
* @param int $maxTime If form is submitted in more than $maxTime seconds, it is invalid
*
* @return boolean
*/
public static function validateNoCaptcha($formId, $submission, $minTime = 3, $maxTime = 86400) {
$valid = TRUE;
$errors = array();
$errors['nodisplay'] = self::validateNoDisplayField($submission, $valid);
$errors['timestamp'] = self::validateTimestampField($submission, $minTime, $maxTime, $valid);
$errors['referer'] = self::validateRefererField($submission, $valid);
$errors['nonce'] = self::validateNonceField($formId, $submission, $valid);
if (!$valid) {
$logger = Logger::getInstance();
$logger->info('Library::NoCaptchaUtil', $errors);
}
return $valid;
}
/**
* Generate HTML for a nodisplay text field
*
* @access private
* @static
*
* @return string HTML for this form field
*/
private static function generateNoDisplayField() {
return '<input type="text" id="nodisplay" class="nocaptcha" style="display:none" name="nodisplay" tabindex="999">';
}
/**
* Validate submission for a nodisplay field
*
* @access private
* @static
*
* @param array $submission An array of form data, keyed by field name
* @param boolean $valid Reference to boolean that stores overall validity
*
* @return string An error message, if any
*/
private static function validateNoDisplayField($submission, &$valid) {
if (! empty($submission['nodisplay'])) {
$valid = FALSE;
return "nodisplay field contained $submission[nodisplay]";
}
}
/**
* Generate HTML for a timestamp hidden field
*
* @access private
* @static
*
* @param string $timestamp Usually the current time
*
* @return string HTML for this form field
*/
private static function generateTimestampField($timestamp) {
return '<input type="hidden" id="timestamp" name="timestamp" value="' . $timestamp . '">';
}
/**
* Validate submission for a timestamp field
*
* @access private
*
* @param array $submission An array of form data, keyed by field name
* @param int $minTime If form is submitted in less than $minTime seconds, it is invalid
* @param int $maxTime If form is submitted in more than $maxTime seconds, it is invalid
* @param boolean $valid Reference to boolean that stores overall validity
*
* @return string An error message, if any
*/
private static function validateTimestampField($submission, $minTime, $maxTime, &$valid) {
$now = time();
if (($now - $submission['timestamp']) < $minTime
|| ($now - $submission['timestamp']) > $maxTime) {
$valid = FALSE;
return "timestamp start: $submission[timestamp] finish: $now difference: " . ($now - $submission['timestamp']) ;
}
}
/**
* Generate HTML for a referer hidden field
*
* @access private
* @static
*
* @param string $referer Concatenated server and uri for the current page - we'll expect this to match the referer when the form is submitted
*
* @return string HTML for this form field
*/
private static function generateRefererField($referer) {
return '<input type="hidden" id="referer" name="referer" value="' . $referer . '">';
}
/**
* Validate submission for a referer field
*
* @access private
* @static
*
* @param array $submission An array of form data, keyed by field name
* @param boolean $valid Reference to boolean that stores overall validity
*
* @return string An error message, if any
*/
private static function validateRefererField($submission, &$valid) {
$referer_test = '';
if (isset($_SERVER['HTTP_REFERER'])) {
$referer_test = preg_replace('/^https?:\/\//', '', $_SERVER['HTTP_REFERER']);
}
if ($submission['referer'] != $referer_test) {
$valid = FALSE;
return "referer submitted: $submission[referer] actual: $referer_test";
}
}
/**
* Generate HTML for a nonce hidden field
*
* @access private
* @static
*
* @param string $formId A unique id for the current form - probably named after the calling function
* @param string $timestamp Usually the current time
* @param string $referer Concatenated server and uri for the current page - we'll expect this to match the referer when the form is submitted
*
* @return string HTML for this form field
*/
private static function generateNonceField($formId, $timestamp, $referer) {
$nonce = $formId . $timestamp . $referer;
return '<input type="hidden" id="nonce" name="nonce" value="' . md5($nonce) . '">';
}
/**
* Validate submission for a nonce field
*
* @access private
* @static
*
* @param string $formId A unique id for the current form - probably named after the calling function
* @param array $submission An array of form data, keyed by field name
* @param boolean $valid Reference to boolean that stores overall validity
*
* @return string An error message, if any
*/
private static function validateNonceField($formId, $submission, &$valid) {
$nonceTest = $formId . $submission['timestamp'] . $submission['referer'];
if ($submission['nonce'] != md5($nonceTest)) {
$valid = FALSE;
return "nonce submitted: $submission[nonce] test: $nonceTest calculated: " . md5($nonceTest);
}
}
}
?>
@firebus
Copy link
Author

firebus commented Mar 30, 2012

A little library that implements a variety of anti-bot techniques for forms.

  • nodisplay is a honeypot field that has style display:none. if it contains data, we reject the submission
  • timestamp logs the timestamp when the form was created. if the form is submitted too quickly, or too late, we suspect a robot is at the keyboard
  • referer logs the URL of the form before its submitted, and expects that URL in the referer header of the submission. some people browse with referer disabled for anonymity, hence this is the field most likely to cause false positives.
  • nonce generates a hash from a formId you provide, the timestamp field, and the referer field. if you don't have the correct nonce we suspect you are not a human.

Citations

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment