Creating your own captcha test, therefore, is likely to be the best alternative. Although it does take some effort to accomplish, it gives you the most flexibility in managing your application.
For our example, we’re going to select a random dictionary word, store it in the user’s session, and then encode it into an image using the XU image-processing functions built into PHP (see Yeea+ aYa_Ve XU for more information) and enabled by default. For more informa- tion on working with XU, see Yeea+ _jaYa`cX T`_eV_e acVdV_eReZ`_d 85Z_ec` .
1. Select a Random Challenge
If you were to give each user the same challenge, you would make answering the challenge by hijacking it easy. You might therefore generate a nonsense string, as illustrated previously in our discussion of using the captchas.net online captcha facility. An alternative that we will demonstrate here is to choose a challenge at random from an existing large repository. (It should be noted, however, that this technique, which again we are showing simply for the sake of demon- stration, makes the captcha more vulnerable to a dictionary-based brute force attack; you might mitigate this vulnerability by using the hashing technique shown earlier, but if you do that then you may just as well generate a string of random characters.) You can often find a list of English words at fdc dYRcV UZTe h`cUd, and for the sake of demonstration we’ll use that as an example. A database might be faster, especially if you have already set up the connection for some other aspect of your application. The code for selecting and storing that random word follows, and can be found also as TRaeTYR8V_VcReVaYa in the Chapter 17 folder of the down- loadable archive of code for Pro PHP Security at Yeea+ hhhRacVddT`^.
-0aYa
TcVReVRdVddZ`_e`de`cVeYVeRcXVeh`cUW`c]ReVc dVddZ`_PdeRce,
W]ReWZ]V`Wh`cUd`_Vh`cUaVc]Z_V UZTeZ`_Rcj. fdc dYRcV UZTe h`cUd,
340 C H A P T E R 1 7 ■ A L L O W I N G O N L Y H U M A N U S E R S
XVeRcR_U`^`WWdVe
e`eR]SjeVd.WZ]VdZkVUZTeZ`_Rcj,
`WWdVe.cR_U!e`eR]SjeVd$#, `aV_eYVWZ]VdVeeYVa`Z_eVc Wa.W`aV_UZTeZ`_Rcjc, WdVV\Wa`WWdVe,
ac`SRS]jZ_eYV^ZUU]V`WRh`cUd`U`eh`cVRUde`XVeRWf]]h`cU WXVedWa,
eRcXVe.WXVedWa, WT]`dVWa,
de`cVeYVh`cUZ_eYVdVddZ`_
PD6DD:@?LeRcXVeN.eRcXVe, TRaeTYR8V_VcReVaYaT`_eZ_fVd
First, you define a constant that holds the path to your big file of words, and then you deter- mine a random point within the file (up to 32 bytes from the end—the number is completely arbitrary, but is intended to be at least the length of one word plus two newline characters). You open the file for reading and set the file pointer to your random point.
Now, you might very well be in the middle of a word, so you use the WXVed function to read from the file pointer to the next newline character (which marks the end of the current word). Then you use WXVed again to get your random word. Once you have it, you tuck it away as a session variable for use on the next request. The eRcXVe is what the user will need to type in from the captcha image.
2. Generate the Image
Now comes the fun part—creating a new image with your word encoded in it. Just for fun, you’ll throw in a few obfuscating lines, and rotate the text a bit to make it harder to scan. If you’re serious about implementing this system, read up on OCR techniques and use your imagination to come up with antipatterns that might help to foil recognition software.
T`_eZ_fVdTRaeTYR8V_VcReVaYa YV]aVcWf_TeZ`_W`cT`]`cd
Wf_TeZ`_^R\VC834`]`cT`]`cZ^RXVl T`]`c.decPcVa]RTVT`]`c, cVU.YViUVTdfSdecT`]`c!#, XcVV_.YViUVTdfSdecT`]`c##, S]fV.YViUVTdfSdecT`]`c%#,
`fe.Z^RXVT`]`cR]]`TReVZ^RXVcVUXcVV_S]fV, cVefc_`fe,
n
SnyderSouthwell_5084.book Page 340 Wednesday, July 27, 2005 9:37 PM
C H A P T E R 1 7 ■ A L L O W I N G O N L Y H U M A N U S E R S 341
fdVR_jeeWW`_e`_j`fcdjdeV^
W`c`fcViR^a]VhVfdVeYV=fTZUR3cZXYeW`_eWc`^eYV;RgRUZdecZSfeZ`_
j`f^RjR]d`WZ_UEE7W`_edZ_ fdc I""C' ]ZS I"" W`_ed EE7
W`_e. fdc ]`TR] [U\"&! [cV ]ZS W`_ed =fTZUR3cZXYeCVXf]RceeW, W`_eDZkV."),
aRUUZ_X.#!,
XV`^VecjSfZ]URS`iW`ch`cUUZ^V_dZ`_dZ_dV]VTeVUW`_eR_UdZkV h`cU3`i.Z^RXVWeSS`iW`_eDZkV!W`_eeRcXVe,
iT``cUZ_ReV`WFCT`c_Vc`Wh`cU h`cU3`iHZUeY.h`cU3`iL#N,
jT``cUZ_ReV`WF=T`c_Vc==T`c_Vc`Wh`cU h`cU3`i9VZXYe.h`cU3`iL"NRSdh`cU3`iL(N, T`_eRZ_VcHZUeY.h`cU3`iHZUeYaRUUZ_X#, T`_eRZ_Vc9VZXYe.h`cU3`i9VZXYeaRUUZ_X#, eVieI.aRUUZ_X,
jT``cUZ_ReV`W==T`c_Vc`Wh`cU eVieJ.T`_eRZ_Vc9VZXYeaRUUZ_X, TRaeTYR8V_VcReVaYaT`_eZ_fVd
In this next section of the code, you create a function that translates standard hexadecimal web colors into the RGB format used by the XU library, registers the color to an image, and returns the color resource.
Then you define the TrueType font you’re going to use. Lucida happens to ship with Java, so it may already be installed on your server, which could otherwise not have any fonts at all on it (particularly if you don’t have an X Windows server installed). Bitstream’s free Vera font may also be available on some systems. Of course, you can always upload your own font to the server.
Next, you create the box that will contain the word. First, you use the Z^RXVWeSS`i function to calculate the size of the bounding box that the word itself (in the specified font and size) requires. This function returns an array of eight values, the x and y coordinates for the upper- left, upper-right, lower-right, and lower-left corners, in that order. You calculate the exact size of that word box from these coordinates (some of which may be negative, depending on the exact characters being rendered), and then add padding on all sides to determine the size of the containing box. Finally, you calculate the x and y coordinates for placing the word in the center of the containing box.
T`_eZ_fVdTRaeTYR8V_VcReVaYa TcVReVeYVZ^RXV
TRaeTYR:^RXV.Z^RXVTcVReVT`_eRZ_VcHZUeYT`_eRZ_Vc9VZXYe,