This tutorial is intended for those who want to ensure that the data retrieved by a form processor PHP is sent by the expected form, not another application or by hand.
(For more about Zend Technologies, please visit www.zend.com)
An understanding form data processing is assumed. A basic knowledge of image creation and manipulation functions is also required.
Introduction
Every site administrator wants to be sure that a real person filled out the form on their site, not a ‘script’. Especially if the form returns valuable information that others would like. This info may be subscription, domain query, currency rates, weather etc. Almost every programmer (with HTTP and sockets knowledge) can build a script to fill out your form automatically. As you know, ‘Grabbing a Web Site’ is a popular tutorial topic on The Net.
You can never completely protect your sources being grabbed, but you can set some traps that scripts can’t jump over. The most widely used trap is a noisy image, which a human’s eye can read easily but no algorhytm can.
The only constraint that we shouldn’t forget is that the text displayed on the image shouldn’t also be written in the source, otherwise another application or script can grab the source and decipher the text from there. Hence we have to key the information by creating a request ID / code pair, and have a data source where this pair is stored.
Creating a unique information key (or “request id”) is not difficult with PHP. Let’s use uniqid(md5(time( ))) which returns a 45-character-length string. And, as a code displayed on image, generate random 6-digit-number with rand(100000, 999999) function. Every time the form displayed, we’ll generate a pair of values, and insert it into database.
Pseudo Code and Flow Diagram
Before displaying the form, the first PHP (form.php) generates a pair of request id-codes, and inserts them into database (1),
then passes the request id to image creating PHP (image.php) on querystring (2).
This PHP queries the database to retrieve the code associated with this request id (3). After retrieval, a noisy image is created with random parameters, then returns the image to the form (4).
form.php shows the image beside the form, and puts a text field asking the client “what do you see in the box”. The request id is also placed in a HIDDEN field in the form. Hidden request id (5) and code entered by user (6) are posted to the processing script (processor.php).
Proccessing script retrieves the text in the same way, with image.php (7), then compares this code with the user input (8).
Data Schema
Create a table named auth_code in database test:
CREATE TABLE auth_code (
request_id VARCHAR(45) NOT NULL PRIMARY KEY,
auth_code CHAR(6) NOT NULL,
status ENUM('W', 'A', 'E', 'N') NOT NULL DEFAULT 'W'
);
Status code abbreviations stands for “Waiting”, “Approved”, “Expired”, “Not Approved” respectively.
Randomization of Image
When creating the image, we should modify as much as parameter we can every time. Those parameters are;
- Colors. Background, font and noise colors. For readibility, there should be a contrast between background and font colors. So, background colors are generated using red, green, blue values below 132, which make darker colors, while font colors’ are generated with RGB values between 191 and 255, which makes lighter colors. Light colors on dark background can be read easily. Noise color are generated relative to background color. See get_random_colors( ) function in image.php
- Font. Font selection can either be random or fixed. If you want the font randomly selected, set $ font_selection variable’s value to “random” and supply the font directory’s path with $ font_folder and a list of available fonts with $ fonts array. Otherwise, set the value to “fixed”. Also supply $ fonts array, and set the $ default_font’s value to the index of default font in this array.
- Text Angle. This can be either random or fixed, too. If you want the text angle randomly change, set $ angle_selection variable’s value to “random” and supply the angle range. If you set 10 to $ max_angle, script will treat this as a unsigned value and change the range to signed equivalent of this value. (like -5..5). Otherwise, set the value to “fixed” and $ default_angle value to default angle.
- Noise Methods. Will the noise effect be achieved, with dots, rectangles or lines? Just set the $ noise_method variable to “line”, “dots” or “rectangle”.Noise count is equal to $ freq variable’s value. A point (x,y) is randomly selected $ freq times. This point is the pixel where the dot is placed (if you chose “dots”), the center of the 3x3 rectangle (if you chose “rectangle”), or the starting point of the line (if you chose “lines”).
The other parameters are not random but can also be set.
- Size of Image. Set $ x as width and $ y as height of image. Also change the WIDTH and HEIGHT attributes of IMG tag calling image.php as SRC on form.php.
- Noise Frequency. How many dots/lines/rectangles will be on the image?
- Font Size.
Sample Codes
form.php
< HTML >
<
HEAD ><
TITLE >Authentication Code < /TITLE >< /
HEAD >
< ? php
// Generate 6 digit random number
$ num = rand(100000, 999999);
$ request_id = uniqid(md5(time( )));
//Connect database and insert request id and number
$ db_conn = @mysql_connect('localhost', '< username >', '< password >') or die('MySQL Server Not Started');
@
mysql_select_db('test') or die('Database not created');$ sql = "INSERT INTO auth_code VALUES('".$ request_id."', '".$ num."', 'W')";
@
mysql_query($ sql, $ db_conn) or die('Query failed of :'.mysql_error( ));mysql_close($ db_conn);
? >
< BODY BGCOLOR="#FFFFFF" >
<
H3 >Authentication Code < /H3 >< HR ><
FORM METHOD="post" ACTION="processor.php" ><
INPUT TYPE="hidden" NAME="RequestId" VALUE= "< ? php echo($ request_id)? > " ><
TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%" ><
TR ><
TD WIDTH="20%" VALIGN="top" >Type the number below:< BR >
<
INPUT TYPE="text" NAME="UserNumber" MAXLENGTH=6 SIZE=20 ><
INPUT TYPE="submit" VALUE="Check Number" >< /
TD ><
TD WIDTH="80%" VALIGN="middle" ><
IMG SRC= "image.php? rid= < ? php echo($ request_id);? > "WIDTH=160 HEIGHT=50 BORDER=1 >< /
TD >< /
TR >< /
TABLE >< /
FORM >< /
BODY >< /
HTML >image.php
< ? php
// Image should expire immediately
header('Content-type:image/png');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
// Gather request id from querystring
$ request_id = isset($ _GET['rid']) ? $ _GET['rid'] : "0";
if (
strlen($ request_id)!=45) $ error_msg= 'Invalid number';
// Connect database and get the number to be displayed
$ db_conn = @mysql_connect('localhost', '< username >', '< pass >') or die('MySQL Service Not Started');
@
mysql_select_db("test") or die('Database not created');
//Construct SQL
$ sql = "SELECT auth_code FROM auth_code WHERE request_id='".$ request_id."' AND status='W'";
$ tmp_rs = @mysql_query($ sql, $ db_conn) or die('Query failed of '.mysql_error( ));
// Really got such request id or someone having fun?
if (mysql_num_rows($ tmp_rs)==0) {
$ error_msg='No matching rows...';
} else {
$ number = mysql_result($ tmp_rs, 0, 0);
}
// Variables
//////////////////////////////////////////////////////////////////////////
$ x = 160; // Image width
$ y = 50; // Image height
$ freq=1000; // Number of noise dots
$ noise_method="dots"; // line | dots | rectangle
$ font_selection = "random"; // random | fixed
$ font_folder = "C:\\\\WINNT\\\\Fonts\\\"; // Path to fonts folder
$ fonts = array("rubstamp.ttf", "flubber.ttf", "folion.ttf",
"tt0922m_.ttf", "gothic13.ttf", "justov.ttf");
$ default_font = 4; // Array index of default font in $ fonts array.
$ angle_selection = "random"; // random | fixed
$ max_angle = 10; // Max angle
$ default_angle = 0; // Default angle.
$ font_size = 40; // Font size in points
//////////////////////////////////////////////////////////////////////////
// Set font
if ($ font_selection=="random") {
$ font = rand(0, count($ fonts));
$ font = $ font_folder.$ fonts[$ font];
} else {
$ font = $ font_folder.$ fonts[$ default_font];
}
// Set Text Angle
if ($ angle_selection=="random") {
$ angle = rand((-1)*($ max_angle/2), ($ max_angle/2));
} else {
$ angle = $ default_angle;
}
// Create image with specified size.
$ img = @ImageCreate($ x, $ y) or die("Couldn't create image");
// Allocate colors
$ black = ImageColorAllocate($ img, 0, 0, 0);
$ white = ImageColorAllocate($ img, 255, 255, 255);
/* Get background , noise and font color randomly from get_random_colors( ) function. This function returns contrary colors for readability.*/
function get_random_colors( ) {
// $ bck = array of background (R,G,B) values
// $ dot = array of noise (R,G,B) values
// $ txt = array of text (R,G,B) values
$ bck=array( ); $ dot=array( ); $ txt=array( );
// i=O = >Red | i=1 = >Green | i=2 = >Blue
for ($ i=0; $ i< 3; $ i++) {
$ x = rand(0,132);
$ y = rand(191,255);
array_push($ bck, $ x);
array_push($ dot, (255-$ x));
array_push($ txt, $ y);
}
// Return array of 3 arrays : [0..2, 0..2]
return array($ bck, $ dot, $ txt);
}
$ rnd_col = get_random_colors( );
$ background = ImageColorAllocate($ img, $ rnd_col[0][0], $ rnd_col[0][1], $ rnd_col[0][2]);
$ dots_color = ImageColorAllocate($ img, $ rnd_col[1][0], $ rnd_col[1][1