isFeatureActive( 'freecap_use_dict' );; // if your server is NOT set up to deny web access to files beginning ".ht" // then you should ensure the dictionary file is kept outside the web directory // eg: if www.foo.com/index.html points to c:\website\www\index.html // then the dictionary should be placed in c:\website\dict.txt // test your server's config by trying to access the dictionary through a web browser // you should NOT be able to view the contents. // can leave this blank if not using dictionary $dict_location = "./.ht_freecap_words"; // used to calculate image width, and for non-dictionary word generation $max_word_length = 6; // text colour // 0=one random colour for all letters // 1=different random colour for each letter $col_type = 1; // maximum times a user can refresh the image // on a 6500 word dictionary, I think 15-50 is enough to not annoy users and make BF unfeasble. // further notes re: BF attacks in "avoid brute force attacks" section, below // on the other hand, those attempting OCR will find the ability to request new images // very useful; if they can't crack one, just grab an easier target... // for the ultra-paranoid, setting it to <5 will still work for most users $max_attempts = 100; // list of fonts to use // font size should be around 35 pixels wide for each character. // you can use my GD fontmaker script at www.puremango.co.uk to create your own fonts // There are other programs to can create GD fonts, but my script allows a greater // degree of control over exactly how wide each character is, and is therefore // recommended for 'special' uses. For normal use of GD fonts, // the GDFontGenerator @ http://www.philiplb.de is excellent for convering ttf to GD // the fonts included with freeCap *only* include lowercase alphabetic characters // so are not suitable for most other uses // to increase security, you really should add other fonts $font_locations = ["./.ht_freecap_font1.gdf","./.ht_freecap_font2.gdf","./.ht_freecap_font3.gdf","./.ht_freecap_font4.gdf","./.ht_freecap_font5.gdf"]; // background: // 0=transparent (if jpg, white) // 1=white bg with grid // 2=white bg with squiggles // 3=morphed image blocks // 'random' background from v1.3 didn't provide any extra security (according to 2 independent experts) // many thanks to http://ocr-research.org.ua and http://sam.zoy.org/pwntcha/ for testing // for jpgs, 'transparent' is white $bg_type = 2; // should we blur the background? (looks nicer, makes text easier to read, takes longer) $blur_bg = true; // for bg_type 3, which images should we use? // if you add your own, make sure they're fairly 'busy' images (ie a lot of shapes in them) $bg_images = ["./.ht_freecap_im1.jpg","./.ht_freecap_im2.jpg","./.ht_freecap_im3.jpg","./.ht_freecap_im4.jpg","./.ht_freecap_im5.jpg"]; // for non-transparent backgrounds only: // if 0, merges CAPTCHA with bg // if 1, write CAPTCHA over bg $merge_type = 1; // should we morph the bg? (recommend yes, but takes a little longer to compute) $morph_bg = true; // you shouldn't need to edit anything below this, but it's extensively commented if you do want to play // have fun, and email me with ideas, or improvements to the code (very interested in speed improvements) // hope this script saves some spam :-) ////////////////////////////////////////////////////// ////// Create Images + initialise a few things ////////////////////////////////////////////////////// // seed random number generator // PHP 4.2.0+ doesn't need this, but lower versions will $seed_func(make_seed()); // how faded should the bg be? (100=totally gone, 0=bright as the day) // to test how much protection the bg noise gives, take a screenshot of the freeCap image // and take it into a photo editor. play with contrast and brightness. // If you can remove most of the bg, then it's not a good enough percentage switch($bg_type) { case 0: break; case 1: case 2: $bg_fade_pct = 65; break; case 3: $bg_fade_pct = 50; break; } // slightly randomise the bg fade $bg_fade_pct += $rand_func(-2,2); // read each font and get font character widths $font_widths = []; for($i=0 ; $i 6 chars in length really. $width = ($max_word_length*(array_sum($font_widths)/sizeof($font_widths))+20); $height = !empty( $_REQUEST['height'] ) ? $_REQUEST['height'] : 75; $im = ImageCreate($width, $height); $im2 = ImageCreate($width, $height); ////////////////////////////////////////////////////// ////// Avoid Brute Force Attacks: ////////////////////////////////////////////////////// if(empty($_SESSION['freecap_attempts'])) { $_SESSION['freecap_attempts'] = 1; } else { $_SESSION['freecap_attempts']++; // if more than ($max_attempts) refreshes, block further refreshes // can be negated by connecting with new session id // could get round this by storing num attempts in database against IP // could get round that by connecting with different IP (eg, using proxy servers) // in short, there's little point trying to avoid brute forcing // the best way to protect against BF attacks is to ensure the dictionary is not // accessible via the web or use random string option if($_SESSION['freecap_attempts']>$max_attempts) { $_SESSION['captcha'] = false; $bg = ImageColorAllocate($im,255,255,255); ImageColorTransparent($im,$bg); $red = ImageColorAllocate($im, 255, 0, 0); // depending on how rude you want to be :-) //ImageString($im,5,0,20,"bugger off you spamming bastards!",$red); // {{{ BIT_MOD ImageString($im,5,15,5,"Please close browser",$red); ImageString($im,5,15,25,"and load page again",$red); ImageString($im,5,15,45,"to retry",$red); // }}} BIT_MOD sendImage($im); } } ////////////////////////////////////////////////////// ////// Functions: ////////////////////////////////////////////////////// function make_seed() { // from http://php.net/srand list($usec, $sec) = explode(' ', microtime()); return (float) $sec + ((float) $usec * 100000); } function rand_color() { global $bg_type,$rand_func; if($bg_type==3) { // needs darker colour.. return $rand_func(10,100); } return $rand_func(60,170); } function myImageBlur($im) { // w00t. my very own blur function // in GD2, there's a gaussian blur function. bunch of bloody show-offs... :-) $width = imagesx($im); $height = imagesy($im); $temp_im = ImageCreateTrueColor($width,$height); $bg = ImageColorAllocate($temp_im,150,150,150); // preserves transparency if in orig image ImageColorTransparent($temp_im,$bg); // fill bg ImageFill($temp_im,0,0,$bg); // anything higher than 3 makes it totally unreadable // might be useful in a 'real' blur function, though (ie blurring pictures not text) $distance = 1; // use $distance=30 to have multiple copies of the word. not sure if this is useful. // blur by merging with itself at different x/y offsets: ImageCopyMerge($temp_im, $im, 0, 0, 0, $distance, $width, $height-$distance, 70); ImageCopyMerge($im, $temp_im, 0, 0, $distance, 0, $width-$distance, $height, 70); ImageCopyMerge($temp_im, $im, 0, $distance, 0, 0, $width, $height, 70); ImageCopyMerge($im, $temp_im, $distance, 0, 0, 0, $width, $height, 70); // remove temp image ImageDestroy($temp_im); return $im; } function sendImage($pic) { // output image with appropriate headers global $output,$im,$im2,$im3; header(base64_decode("WC1DYXB0Y2hhOiBmcmVlQ2FwIDEuNCAtIHd3dy5wdXJlbWFuZ28uY28udWs=")); switch($output) { // add other cases as desired case "jpg": header("Content-Type: image/jpeg"); ImageJPEG($pic); break; case "gif": header("Content-Type: image/gif"); ImageGIF($pic); break; case "png": default: header("Content-Type: image/png"); ImagePNG($pic); break; } // kill GD images (removes from memory) ImageDestroy($im); ImageDestroy($im2); ImageDestroy($pic); if(!empty($im3)) { ImageDestroy($im3); } exit(); } ////////////////////////////////////////////////////// ////// Choose Word: ////////////////////////////////////////////////////// if($use_dict==1) { // load dictionary and choose random word $words = @file($dict_location); $word = strtolower($words[$rand_func(0,sizeof($words)-1)]); // cut off line endings/other possible odd chars $word = preg_replace("/[^a-z]/","",$word); // might be large file so forget it now (frees memory) $words = ""; unset($words); } else { // based on code originally by breakzero at hotmail dot com // (http://uk.php.net/manual/en/function.rand.php) // generate pseudo-random string // doesn't use ijtf as are easily mistaken // I'm not using numbers because the custom fonts I've created don't support anything other than // lowercase or space (but you can download new fonts or create your own using my GD fontmaker script) $consonants = 'bcdghklmnpqrsvwxyz'; $vowels = 'aeuo'; $word = ""; $wordlen = $rand_func(5,$max_word_length); for($i=0 ; $i<$wordlen ; $i++) { // don't allow to start with 'vowel' if($rand_func(0,4)>=2 && $i!=0) { $word .= $vowels[$rand_func(0,strlen($vowels)-1)]; } else { $word .= $consonants[$rand_func(0,strlen($consonants)-1)]; } } } // save hash of word for comparison // using hash so that if there's an insecurity elsewhere (eg on the form processor), // an attacker could only get the hash // also, shared servers usually give all users access to the session files // echo `ls /tmp`; and echo `more /tmp/someone_elses_session_file`; usually work // so even if your site is 100% secure, someone else's site on your server might not be // hence, even if attackers can read the session file, they can't get the freeCap word // (though most hashes are easy to brute force for simple strings) $_SESSION['captcha'] = $hash_func($word); ////////////////////////////////////////////////////// ////// Fill BGs and Allocate Colours: ////////////////////////////////////////////////////// // set tag colour // have to do this before any distortion // (otherwise colour allocation fails when bg type is 1) $tag_col = ImageColorAllocate($im,10,10,10); $site_tag_col2 = ImageColorAllocate($im2,0,0,0); // set debug colours (text colours are set later) $debug = ImageColorAllocate($im, 255, 0, 0); $debug2 = ImageColorAllocate($im2, 255, 0, 0); // set background colour (can change to any colour not in possible $text_col range) // it doesn't matter as it'll be transparent or coloured over. // if you're using bg_type 3, you might want to try to ensure that the color chosen // below doesn't appear too much in any of your background images. $bg = ImageColorAllocate($im, 254, 254, 254); $bg2 = ImageColorAllocate($im2, 254, 254, 254); // set transparencies ImageColorTransparent($im,$bg); // im2 transparent to allow characters to overlap slightly while morphing ImageColorTransparent($im2,$bg2); // fill backgrounds ImageFill($im,0,0,$bg); ImageFill($im2,0,0,$bg2); if($bg_type!=0) { // generate noisy background, to be merged with CAPTCHA later // any suggestions on how best to do this much appreciated // sample code would be even better! // I'm not an OCR expert (hell, I'm not even an image expert; puremango.co.uk was designed in MsPaint) // so the noise models are based around my -guesswork- as to what would make it hard for an OCR prog // ideally, the character obfuscation would be strong enough not to need additional background noise // in any case, I hope at least one of the options given here provide some extra security! $im3 = ImageCreateTrueColor($width,$height); $temp_bg = ImageCreateTrueColor($width*1.5,$height*1.5); $bg3 = ImageColorAllocate($im3,255,255,255); ImageFill($im3,0,0,$bg3); $temp_bg_col = ImageColorAllocate($temp_bg,255,255,255); ImageFill($temp_bg,0,0,$temp_bg_col); // we draw all noise onto temp_bg // then if we're morphing, merge from temp_bg to im3 // or if not, just copy a $widthx$height portion of $temp_bg to $im3 // temp_bg is much larger so that when morphing, the edges retain the noise. if($bg_type==1) { // grid bg: // draw grid on x for($i=$rand_func(6,20) ; $i<$width*2 ; $i+=$rand_func(10,25)) { ImageSetThickness($temp_bg,$rand_func(2,6)); $text_r = $rand_func(100,150); $text_g = $rand_func(100,150); $text_b = $rand_func(100,150); $text_colour3 = ImageColorAllocate($temp_bg, $text_r, $text_g, $text_b); ImageLine($temp_bg,$i,0,$i,$height*2,$text_colour3); } // draw grid on y for($i=$rand_func(6,20) ; $i<$height*2 ; $i+=$rand_func(10,25)) { ImageSetThickness($temp_bg,$rand_func(2,6)); $text_r = $rand_func(100,150); $text_g = $rand_func(100,150); $text_b = $rand_func(100,150); $text_colour3 = ImageColorAllocate($temp_bg, $text_r, $text_g, $text_b); ImageLine($temp_bg,0,$i,$width*2, $i ,$text_colour3); } } else if($bg_type==2) { // draw squiggles! $bg3 = ImageColorAllocate($im3,255,255,255); ImageFill($im3,0,0,$bg3); ImageSetThickness($temp_bg,4); for($i=0 ; $i$prev_y-2); ImageCopy($im, $im2, $i, $y_pos, $i, 0, $font_pixelwidth, $height); // for debug: //ImageRectangle($im,$i,$y_pos+10,$i+$font_pixelwidth,$y_pos+70,$debug); } // for debug: //sendImage($im); ImageFilledRectangle($im2,0,0,$width,$height,$bg2); // randomly morph each character individually on x-axis // this is where the main distortion happens // massively improved since v1.2 $y_chunk = 1; $morph_factor = .1; $morph_x = 0; for($j=0 ; $j