by a link from WorldConnect by a link from Ancestry.com heard about it from the Davenport mailing list as recommended by another family researcher by looking for us on Bing by looking for us on Google by looking for us on MSN by following a link from some other page by accident (i.e., in a drunken stupor) REFOPTIONS ; // Set Javascript Variables (escape all ') // This is the text for the password pop-ups $promptString1 = "And the password is..."; $promptString2 = "Hmmm... how about... Abracadabra?"; $alertString = "Nope! Who do you think you are? Kevin Mitnick?? You know, maybe you should think about getting outta the hacking biz... hey, how about a job as a court reporter? It says here that you can make up to $30k a year! And you wouldn\\'t have to worry about hacking all those silly passwords, either..."; ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // File Locations (required) // // Some necessary customizatons: // Do NOT change these three lines $webroot = preg_replace('%/[^/]*$%', '', $_SERVER['SCRIPT_NAME']); // result: /guestbook $fileroot = preg_replace('%/[^/]*$%', '', $_SERVER['SCRIPT_FILENAME']); // result: /path/to/htmlroot/guestbook $htmlroot = preg_replace("%$webroot%", '', $fileroot); // result: /path/to/htmlroot // DO change these four lines if you'd like to customize it // Ensure write-permissions for these automatically generated files $guestbookdata = "$fileroot/guestdata.txt"; $guestlogname = "guestlog.html"; $guestlog = "$fileroot/$guestlogname"; $tmpguestbookdata = "$fileroot/tmpguestdata.txt"; $tmpguestlog = "$fileroot/tmpguestlog.html"; // Our full URLs // Do NOT change this line $server = $_SERVER['SERVER_NAME']; // result: www.yoursite.com // DO change these two lines, if you like // Don't forget the trailing slash if it's *not* a file (i.e., dir) $homeurl = "http://$server/"; // e.g., http://yoursite.com/ $guestbookurl = "http://$server/" . ltrim($webroot,"/") ."/"; // e.g., http://yoursite.com/guestbook/ $guestlogurl = "http://$server/" . ltrim($webroot,"/") ."/$guestlogname"; // e.g., http://yoursite.com/guestbook/guestlog.html $basehref = $homeurl; // E.g., http://www.sethi.org/guestbook/ // DO change these three lines, if you like // Some included files // Ensure valid (relative) path (leave blank if none) $myssipath = "$htmlroot/ssi"; // Get our SSI actual root dir path (optional) $myssi = "/ssi"; // Get our SSI web dir (optional) $navbarpath = "$myssipath/navbar.html"; // Navigational Bar path (optional) $navbar = "$myssi/navbar.html"; // Navigational Bar (optional -- LEGACY -- UNNEEDED) $jsfilepath = "$myssipath/js-common.js"; // Extra Javascript Stuff path (optional) $jsfile = "$myssi/js-common.js"; // Extra Javascript Stuff (optional) // Do NOT change these four lines unless you really know what you're doing // Perl (legacy) stuff (no need to mess with these, really) // Change $cgiurl if your script is different from your guestbook URL $cgiurl = $guestbookurl; // e.g., http://yoursite.com/guestbook/gb.php $postGuestbook = $cgiurl; $datafile = $guestbookdata; $file_splitter = "--------------------------------------------------"; $downloadLink = "http://www.sethi.org/tools/releases/guestbook-index.phps"; ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // Authentication Functionality (necessary) // // DO change these three lines (to make your site more secure) $session_label = 'kd984z905kjf8u9834'; // Session Code Label (CHANGE THESE!) $post_label = 'id84nt34o8fkn39dsk'; // Post Code Label (CHANGE THESE!) $max_num_label = 'max_attempts'; // Maximum Attempts Label (change for yourself!) // DO change these three lines (if you know what you're doing) $logfile = "/tmp/bad_bot.log"; // Bad_Bot log file (brute force) $numChars = 6; // Size of String: default 5 (in case a spambot tries to set size=0 parameter) $max_attempts = 4; // Max number of allowed attempts (brute force) // (slip-through attacks are more deadly so leave this relatively big) ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // GuestBook Functionality (recommended) // // Our guess to what our email address ($recipient) should be -- // Please change if the guess ain't no good! :) // Get just Top Level Domain and construct email: guestbook@top_level_domain.com $domain = explode('.',$server); $tld = $domain[count($domain)-2] .".". $domain[count($domain)-1]; $recipient = 'kdd@localhost'; // Legacy: not used $separator = 1; // 1 = ; 0 = ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // GuestBook Appearance (optional) // // Title text/body: if ( !isset($titleText) || empty($titleText) ) $titleText = "Thank you for visiting us... please sign in below"; if ( !isset($titleBody) || empty($titleBody) ) $titleBody = <<Add your unique voice to our archives! TITLEBODY ; // Set addForm bgcolor: $bcol = "#CCCCCC"; // Set the default background colour: if (! isset( $_GET['bg'] ) ) { $bg="E0E0E0"; } else { $bg=$_GET['bg']; } // Make bgcolour array: $bgcolours = array ( "E0E0E0", "AntiqueWhite", "DarkTurquoise", "LightGrey", "MediumAquamarine", "SeaShell", "Silver" ); // Set default format: if (! isset( $_GET['f'] ) ) { $f="Elegant"; } else { $f=$_GET['f']; } // Make format array: $formats = array ( "Elegant", "Simple", "Standard" ); // $number is number of entries per page if (! isset( $_GET['number'] ) ) { $number = 50; } else { $number=$_GET['number']; } ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // GuestBook Appearance Template (editing not recommended) // (stuff that will go out into template files later) // ///// Title & Header function PrintTitleHeader() { // Declare my globals (so it doesn't just create a new, local var) global $titleTag, $titleText, $transpgif; print << $titleTag $titleText TITLEHEADER ; } ///// Top navigational table function PrintTopNav() { // Declare my globals (so it doesn't just create a new, local var) global $number, $no_entries, $bg, $f, $cgiurl; print << First $number entries All $no_entries entries TOPNAV ; } ///// The Add Form function PrintAddForm() { // Declare my globals (so it doesn't just create a new, local var) global $postGuestbook, $bcol, $transpgif, $pwidth, $referrer_opt; global $name, $email, $url, $city, $state, $country, $referrer, $orig_comments, $comment, $private, $password, $ip; global $vImage, $code_submitted, $myName; if (!$url) {$url = "http://";} if (!$country) {$country = "USA";} print << Please enter at least your name along with the correct authentication code and your comment(s)... Thanks! Your Name: E-mail: Homepage URL: Geographical Info: City, State, Country , , How did you stumble into our Virtual Home? $referrer_opt Comments: $orig_comments Make it private? If private, please specify password (case sensitive) Sorry, can't use HTML in a private comment. Also, if no password is specified, default password will be assigned. Please enter the code you see above: ADDFORM2 ; $vImage->showCodBox(1); print <<(Or hit REFRESH if it's broken) * ADDFORM3 ; } ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // // No need to make any changes below here // ////////////////////////////////////////////////////////////////////// /////////////// Set Some Variables /////////////// // 2007-06-13: For PHP5, set the timezone: date_default_timezone_set("America/Los_Angeles"); // Figure out the date (e.g., Tuesday, June 1, 1999 at 7:11 PM and 6/4/1999 17:42) $dayOfWeek = date("l"); $monthOfYear = date("F"); $thisMonth = date("n"); $thisDay = date("j"); $yearNumber = date("Y"); $mytime = date("g:i A"); $thisHour = date("G"); $thisMinute = date("i"); $myLongDate = "$dayOfWeek, $monthOfYear $thisDay, $yearNumber at $mytime"; $myShortDate = "$thisMonth/$thisDay/$yearNumber $thisHour:$thisMinute"; // Get the Date for Entry (legacy code) $date = $myLongDate; $shortdate = $myShortDate; $myName = $_SERVER['PHP_SELF']; // result: /guestbook/index.php ////////////////////////////////////////////////////////////////////// // // Library: authenticate_image.phtml // Version: 0.3.8 // Used in http://www.sethi.org/guestbook/ // Implemented: Implemented in same file (at the end) // // Description: This class generates an image with random text that // can be used as part of a CAPTCHA (see below) system and used as image // verification for forms of any sort (this was developed in the context // of The Sethi Family GuestBook. It incorporates elements designed to // foil bots by confusing OCR software and also uses strong PHP session // security in order to disallow them getting around it (e.g., preventing // brute force attacks, slip-through attempts, etc.). // Also, disallows blank sessionIDs as a great anti-spam technique for guestbooks, // etc. to use against spambots that use PHP's vulnerability, a la // sending fake (/guestbook/?PHPSESSID=e295d8b99fb4a8b38bd496373391b803&number=724) // requests with blank sessionIDs. // // License: // GPL & Postcard-Ware! If you like this program and // use it, drop me a postcard or an email or just sign // our guestbook and tell me how great I am. :) And // hey, if you *really* like it, don't worry about // designing that shrine dedicated to my greatness... a // simple link back to our homepage should suffice. // // **************************************************** // Also, if you do use this script, please forward your // guestbook and homepage URLs to me at rickys@sethi.org // as I'll be compiling a page of users of the script // from around the world (should help pump some traffic // your way, too). // **************************************************** // // Credits: // Original idea based on Rafael Machado Dohms' vImage in Portugese // http://planeta.terra.com.br/informatica/d2000/vImage/vImage_withexamples.zip // // Author: // Ricky J. Sethi (rickys@sethi.org) // http://www.sethi.org/ // // References: Some links and references for CAPTCHA (image verification): // * CAPTCHA decoder: http://sam.zoy.org/pwntcha/ // * Basic CAPTCHA: http://captchas.net/sample/php/ // * Basic image authentication: http://www.php-mysql-tutorial.com/user-authentication/image-verification.php // * Basic image authentication: http://www.weberdev.com/get_example-4067.html // * Also see veriword and freecap and PHP XSS attacks (http://www.hardened-php.net/advisory_012006.112.html) // // Changes: // This version now implements the following: // * 2006-05-05: // * Changed session_labels, etc. // * Added $sessionCode, $postCode initialization // * Initialized class vars in constructor using GLOBALS // * Used $_GET instead of $_SERVER to do check // * Increased num of max_attempts and numChars // // * 2006-05-03: Changed implementation to be in same file (at the end) // // * Logging of successful, bad_bot (brut force), and slip-through attempts // * Code to actually prevent slipthrough attacks (most effective anti-spam technique) // * Stops brute force attacks of re-using session across attempts or vhosts // // TODO: // * // ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // // Changes: // // 2005-08-07: Last time of update/change before better time accounting // Made my own changes to this... messed with class to make it less stringent // Added ?php everywhere // Need img.phtml?size=5 to create the image // Use the image in form.phtml and verify in verify.phtml // Combine form.phtml and verify.phtml into one file: combined.phtml; added $code_submitted // // 2006-04-30: Added brute force attack countering based on // freecap.php (basically, if they try to use the session's stored // code to keep making spam entries, we instead reset the session // after $max_attempts and then also log the attempt in the // /tmp/bad_bot.log file // // 2006-05-01: Added logging successful, bad_bot, and slip-through // (most vicious/effective) attempts. Also added code to actually // catch and prevent slipthrough attacks (BEST anti-spam technique). // ////////////////////////////////////////////////////////////////////// class vImage { ////////////////////////////////////////////////////////////////////// // // Customizations -- make changes below (also set via globals, above) // ////////////////////////////////////////////////////////////////////// var $h = 30; // Height of Image: default 20; var $colBG = "188 220 231"; // Background colour setting var $colTxt = "60 150 120"; // Text colour setting (black->greenish blue) var $colBorder = "0 128 192"; // Border colour setting var $charx = 20; // Lateral space of each char var $numCirculos = 5; // Number of random circles ////////////////////////////////////////////////////////////////////// // // No need to make any changes below here // ////////////////////////////////////////////////////////////////////// var $logfile = "/tmp/bad_bot.log"; // Bad_Bot log file (brute force) var $doBadBotLogging = 0; // Boolean to see if we should log bad robot attempts (1 = Yes, 0 = No) var $numChars = 5; // Size of String: default 5 (in case a spambot tries to set size=0 parameter) var $max_attempts = 5; // Max number of allowed attempts (brute force) // (slip-through attacks are more deadly so leave this relatively big) var $session_label = 'xyzpldlkjoj_23ljks'; // Session Code Label (change for yourself!) var $post_label = 'ljs_lj231vuewo10_m'; // Post Code Label (change for yourself!) var $max_num_label = 'max_attempts'; // Maximum Attempts Label (change for yourself!) var $num_attempts = 0; // Number of attempts they've already tried (brute force) var $w = 20; // Width of the image (automatically calculated below) var $DEBUG = 5; // Are we DEBUG'ing? var $sessionCode = ''; // Initialize the php cookie's session Code storage variable var $postCode = ''; // Initialize the user's posted Code storage variable var $image_display_label = 'display_image'; // Set display image variable label var $image_size_label = 'size'; // Set image size variable label // Constructor; just starts the server-side cookie session function vImage(){ // Initialize local variables to globals: $this->logfile = $GLOBALS['logfile']; $this->doBadBotLogging = $GLOBALS['doBadBotLogging']; $this->numChars = $GLOBALS['numChars']; $this->max_attempts = $GLOBALS['max_attempts']; $this->session_label = $GLOBALS['session_label']; $this->post_label = $GLOBALS['post_label']; $this->max_num_label = $GLOBALS['max_num_label']; // Set the session to expire in 15 mins... //session_save_path("."); // Save cookies in local directory? session_cache_expire(15); session_start(); } // Generate the text (string passcode) // Gerar is to generate in Portugese function gerText($num){ // Check passed size of string to make sure it's bigger than min numChars if ( ($num != '') && ($num > $this->numChars) ) $this->numChars = $num; // To generate random strings $this->texto = $this->gerString(); // SET the code we just generated in their server-side cookie for later comparison $_SESSION["$this->session_label"] = $this->texto; } // Load the codes (postCode is what they guessed; sessionCode is what we set) function loadCodes() { $this->postCode = isset($_POST["$this->post_label"]) ? $_POST["$this->post_label"] : ""; $this->sessionCode = isset($_SESSION["$this->session_label"]) ? $_SESSION["$this->session_label"] : ""; } ////////////////////////////////////////////////////// // Check the code; return true or false // Also, counter slip-through attempts by spambots in here! // (slipthrough spammers use a blank sessionCode in fake sessionID) ////////////////////////////////////////////////////// function checkCode() { if (isset($this->postCode)) $this->loadCodes(); // Some spammers got through with a blank code! if (empty($this->sessionCode)) { // Log slip-through attempts... if ($this->DEBUG > 0) { $this->LogAttempt("slipthrough"); } return false; } if ($this->postCode == $this->sessionCode) { // 2006-06-13: prevent piggy-backing (session hijacking) on a successful post attack // RESET the code before returning on a successful post // Do this to prevent a piggy-backing attack $this->gerText($this->numChars); // Log successful attempts? if ($this->DEBUG > 3) { $this->LogAttempt("success"); } return true; } else { // Log unsuccessful attempts? if ($this->DEBUG > 3) { $this->LogAttempt("nomatch"); } // Counter against a potential brute force attack $this->CounterBruteForce(); return false; } } ////////////////////////////////////////////////////// // Counter Potential Brute Force Attacks: ////////////////////////////////////////////////////// function CounterBruteForce() { if(empty($_SESSION["$this->max_num_label"])) { $_SESSION["$this->max_num_label"] = 1; $this->num_attempts = 1; } else { $_SESSION["$this->max_num_label"]++; $this->num_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["$this->max_num_label"] > $this->max_attempts) { // RESET the code here immediately! $this->gerText($this->numChars); // depending on how rude you want to be :-) //ImageString($im,5,0,20,"bugger off you spamming bastards!",$red); // Possibly send em to bad_bot page? Or log to a file??? if ($this->DEBUG > 0) { $this->LogAttempt("bad_bot"); } } } } // Log Bad_Bot Attempt! function LogAttempt($kind) { if ( $this->doBadBotLogging ) { // Open the filehandle (append to end) $fh = fopen($this->logfile, 'a') or die ("Couldn't open $this->logfile\n"); fwrite($fh, "$kind | ". $_SERVER['REMOTE_ADDR'] ." | ". date("Y-m-d H:i:s") ." | ". $this->num_attempts ." | Session:". $this->sessionCode ." | Post:". $this->postCode ."\n"); fclose($fh); } } function showCodBox($mode=0,$extra=''){ $str = "post_label\" ".$extra." > "; if ($mode) echo $str; else return $str; } function showImage(){ $this->gerImage(); header("Content-type: image/png"); ImagePng($this->im); } function gerImage(){ // To calculate size to fit text $this->w = ($this->numChars*$this->charx) + 40; // 5px de cada lado, 4px por char // To Create img // $this->im = imagecreate($this->w, $this->h); $this->im = imagecreatefrompng("background.png"); //to draw deep edge and imagefill($this->im, 0, 0, $this->getColor($this->colBorder)); //imagefilledrectangle ( $this->im, 1, 1, ($this->w-2), ($this->h-2), $this->getColor($this->colBG) ); // #desenhar circulos (to draw circles?) for ($i=1;$i<=$this->numCirculos;$i++) { $randomcolor = imagecolorallocate ($this->im , rand(100,255), rand(100,255),rand(100,255)); imageellipse($this->im,rand(0,$this->w-10),rand(0,$this->h-3), rand(25,50),rand(20,40),$randomcolor); } //To write text $ident = 20; for ($i=0;$i<$this->numChars;$i++){ $char = substr($this->texto, $i, 1); $font = rand(3,5); $y = round(($this->h-15)/2); // want random colored characters too // if still not good enough - go to http://us3.php.net/manual/en/function.imageloadfont.php //$col = $this->getColor($this->colTxt); $col = imagecolorallocate ($this->im , rand(50,250), rand(120,200),rand(120,200)); if (($i%2) == 0){ imagechar ( $this->im, $font, $ident, $y, $char, $col ); }else{ imagechar ( $this->im, $font, $ident, $y, $char, $col ); } $ident = $ident+$this->charx; } } function getColor($var){ $rgb = explode(" ",$var); $col = imagecolorallocate ($this->im, $rgb[0], $rgb[1], $rgb[2]); return $col; } function gerString(){ rand(0,time()); $possible="abcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()-+:;<>{}~,.?"; $str = ""; while(strlen($str)<$this->numChars) { $str.=substr($possible,(rand()%(strlen($possible))),1); } $txt = $str; return $txt; } } // End vImage class /////////////// Image Authentication Globals /////////////// ///// Image Authentication Header $vImage = new vImage(); // Code submitted just tells us whether or not they've attempted to submit a code yet if (! isset( $code_submitted ) ) { $code_submitted = 0; } else { $vImage->loadCodes(); } // Are we being called to show the image? if ( !empty($_GET["$vImage->image_size_label"]) ) { $vImage->gerText($_GET["$vImage->image_size_label"]); $vImage->showImage(); exit(); } ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // // Start Guestbook Output // ////////////////////////////////////////////////////////////////////// /////////////// Parse The Input /////////////// // Get our variables IF there was a post: // 2007-06-13: Replaced $HTTP_POST_VARS with $_POST if ( !empty( $_POST ) ) { reset ($_POST); while (list ($key, $val) = each ($_POST)) { // After hacking attempt, make checkCode(); // Print the Blank Response Subroutines in case no name or comment: if (!$theCode || !$comment || !$name) { $code_submitted = 0; MissingInfo(); } // Actual Output of Comments/Entries Now (if not exited) WriteDataFile(); // Mail a copy of the comment to us if ($mail == '1') { SendMeMail(); } // Mail a copy of the comment to the signer if ($remote_mail == '1' && $email) { SendThemMail(); } // Print Out Initial Output Location Heading if ($redirection == '1') { // Redirect header header ("Location: $guestbookurl"); exit; // Make sure that code below does not get executed when we redirect. } else { No_Redirection(); } } // Keep fxns inline for ease of distribution //require ("guestbook-fxns.inc"); //////////////////// Start Output Functions //////////////////// // // // // Fxn to Verify Form Variables // function VerifyFormVariables() { // Declare my globals (so it doesn't just create a new, local var) global $FORM, $orig_comments, $comment, $guestbookurl; global $date, $shortdate, $cgiurl, $uselog, $guestlog; global $defaultpassword; // 2007-01-31: Thanks to Curran Nachbar Schiefelbein global $allow_html, $line_breaks; // 2008-02-07: using it but not added in! // 2007-06-13: added isset() check if ( isset($FORM{'url'}) && preg_match("%^http://$%",$FORM{'url'}) ) { $FORM{'url'} = ""; } // Fix the $referrername variable's sentence terminator: // 2007-06-13: added isset() check if ( isset($FORM{'referrername'}) && preg_match("%[^\!]$%",$FORM{'referrername'})) { $FORM{'referrername'} .= "."; } // Should we allow html tags? if (isset($allow_html) && $allow_html == 0) { // Could replace all < & > with > & < instead (to keep context?) $FORM{'comments'} = preg_replace("%<([^>]|\n)*>%", "", $FORM{'comments'}); } /* // Replace all remaining < and > characters if (isset($allow_html) && $allow_html == 0) { $FORM{'comments'} = preg_replace("%<%", "<", $FORM{'comments'}); $FORM{'comments'} = preg_replace("%>%", ">", $FORM{'comments'}); } */ if (isset($line_breaks) && $line_breaks == 1) { $FORM{'comments'} = preg_replace("%\cM\n/\n%g","",$FORM{'comments'}); } if ( isset( $FORM{'private'} ) ) { if (!$FORM{'password'}) { $FORM{'password'} = $defaultpassword; } // Parse comment for weird characters: // Parse for quotes (all varieties) and replace with \' // Strange, had to separate these out because of multiple \\'s in comment // Order is important $FORM{'comments'} = preg_replace("%['\"]%", "\\'", $FORM{'comments'}); $FORM{'comments'} = preg_replace("%[\`]%", "\\'", $FORM{'comments'}); // Parse html out of private comments by default $FORM{'comments'} = preg_replace("%<([^>]|\n)*>%", "", $FORM{'comments'}); // Parse for ^M's (the \r\n that Windows doesn't understand) $FORM{'comments'} = preg_replace("%\r?\n%", "\\n", $FORM{'comments'}); $orig_comments = $FORM{'comments'}; $password = $FORM{'password'}; $comment = <<Private Guestbook Comment PRIVATECOMMENT ; // Hose trailing newline from above <", $FORM{'comments'}); $orig_comments = $FORM{'comments'}; $comment = $FORM{'comments'}; } } // // Fxn to show missing information: // function MissingInfo() { // Declare my globals (so it doesn't just create a new, local var) global $name, $email, $url, $city, $state, $country, $referrer, $comment, $private, $password, $ip; global $date, $shortdate, $uselog, $bcol; global $guestbookurl, $cgiurl, $homeurl, $homepageText; global $vImage; // What's missing? if (!$comment) { $missingitem = "The Comments Section"; } elseif (!$name) { $missingitem = "Your Name"; ////////////////////////////////////////////////////////////////////// // 2006-07-01: This HAS to be the LAST CHECKED item otherwise it'll // always report that incorrect authentication code was // entered since we re-set authentication code even on // a successful submission (which was done to prevent // piggy-back/session hijacking attacks by spambots). ////////////////////////////////////////////////////////////////////// } elseif (!$vImage->checkCode()) { $missingitem = "Correct Authentication Code"; } // Print the form: print << $missingitem wasn't entered $missingitem wasn't entered! How could you forget to enter $missingitem??? Are you taking drugs?? Do I have to give you that lecture again?? Try it again... and for Pete's sake, get it right this time, willya?!?! Enter Info Below: Fill in the blanks to add to our guestbook. Please enter at least your name along with the correct authentication code and your comment(s)... Thanks! MISSINGINFO ; // Print the actual form now $foo = $bcol; $bcol = "D0D0D0"; PrintAddForm(); $bcol = $foo; print << * Back to the Guestbook Entries * Back to $homepageText
////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // GuestBook Appearance (optional) // // Title text/body: if ( !isset($titleText) || empty($titleText) ) $titleText = "Thank you for visiting us... please sign in below"; if ( !isset($titleBody) || empty($titleBody) ) $titleBody = <<Add your unique voice to our archives! TITLEBODY ; // Set addForm bgcolor: $bcol = "#CCCCCC"; // Set the default background colour: if (! isset( $_GET['bg'] ) ) { $bg="E0E0E0"; } else { $bg=$_GET['bg']; } // Make bgcolour array: $bgcolours = array ( "E0E0E0", "AntiqueWhite", "DarkTurquoise", "LightGrey", "MediumAquamarine", "SeaShell", "Silver" ); // Set default format: if (! isset( $_GET['f'] ) ) { $f="Elegant"; } else { $f=$_GET['f']; } // Make format array: $formats = array ( "Elegant", "Simple", "Standard" ); // $number is number of entries per page if (! isset( $_GET['number'] ) ) { $number = 50; } else { $number=$_GET['number']; } ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // GuestBook Appearance Template (editing not recommended) // (stuff that will go out into template files later) // ///// Title & Header function PrintTitleHeader() { // Declare my globals (so it doesn't just create a new, local var) global $titleTag, $titleText, $transpgif; print << $titleTag $titleText TITLEHEADER ; } ///// Top navigational table function PrintTopNav() { // Declare my globals (so it doesn't just create a new, local var) global $number, $no_entries, $bg, $f, $cgiurl; print << First $number entries All $no_entries entries TOPNAV ; } ///// The Add Form function PrintAddForm() { // Declare my globals (so it doesn't just create a new, local var) global $postGuestbook, $bcol, $transpgif, $pwidth, $referrer_opt; global $name, $email, $url, $city, $state, $country, $referrer, $orig_comments, $comment, $private, $password, $ip; global $vImage, $code_submitted, $myName; if (!$url) {$url = "http://";} if (!$country) {$country = "USA";} print << Please enter at least your name along with the correct authentication code and your comment(s)... Thanks! Your Name: E-mail: Homepage URL: Geographical Info: City, State, Country , , How did you stumble into our Virtual Home? $referrer_opt Comments: $orig_comments Make it private? If private, please specify password (case sensitive) Sorry, can't use HTML in a private comment. Also, if no password is specified, default password will be assigned. Please enter the code you see above: ADDFORM2 ; $vImage->showCodBox(1); print <<(Or hit REFRESH if it's broken) * ADDFORM3 ; } ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // // No need to make any changes below here // ////////////////////////////////////////////////////////////////////// /////////////// Set Some Variables /////////////// // 2007-06-13: For PHP5, set the timezone: date_default_timezone_set("America/Los_Angeles"); // Figure out the date (e.g., Tuesday, June 1, 1999 at 7:11 PM and 6/4/1999 17:42) $dayOfWeek = date("l"); $monthOfYear = date("F"); $thisMonth = date("n"); $thisDay = date("j"); $yearNumber = date("Y"); $mytime = date("g:i A"); $thisHour = date("G"); $thisMinute = date("i"); $myLongDate = "$dayOfWeek, $monthOfYear $thisDay, $yearNumber at $mytime"; $myShortDate = "$thisMonth/$thisDay/$yearNumber $thisHour:$thisMinute"; // Get the Date for Entry (legacy code) $date = $myLongDate; $shortdate = $myShortDate; $myName = $_SERVER['PHP_SELF']; // result: /guestbook/index.php ////////////////////////////////////////////////////////////////////// // // Library: authenticate_image.phtml // Version: 0.3.8 // Used in http://www.sethi.org/guestbook/ // Implemented: Implemented in same file (at the end) // // Description: This class generates an image with random text that // can be used as part of a CAPTCHA (see below) system and used as image // verification for forms of any sort (this was developed in the context // of The Sethi Family GuestBook. It incorporates elements designed to // foil bots by confusing OCR software and also uses strong PHP session // security in order to disallow them getting around it (e.g., preventing // brute force attacks, slip-through attempts, etc.). // Also, disallows blank sessionIDs as a great anti-spam technique for guestbooks, // etc. to use against spambots that use PHP's vulnerability, a la // sending fake (/guestbook/?PHPSESSID=e295d8b99fb4a8b38bd496373391b803&number=724) // requests with blank sessionIDs. // // License: // GPL & Postcard-Ware! If you like this program and // use it, drop me a postcard or an email or just sign // our guestbook and tell me how great I am. :) And // hey, if you *really* like it, don't worry about // designing that shrine dedicated to my greatness... a // simple link back to our homepage should suffice. // // **************************************************** // Also, if you do use this script, please forward your // guestbook and homepage URLs to me at rickys@sethi.org // as I'll be compiling a page of users of the script // from around the world (should help pump some traffic // your way, too). // **************************************************** // // Credits: // Original idea based on Rafael Machado Dohms' vImage in Portugese // http://planeta.terra.com.br/informatica/d2000/vImage/vImage_withexamples.zip // // Author: // Ricky J. Sethi (rickys@sethi.org) // http://www.sethi.org/ // // References: Some links and references for CAPTCHA (image verification): // * CAPTCHA decoder: http://sam.zoy.org/pwntcha/ // * Basic CAPTCHA: http://captchas.net/sample/php/ // * Basic image authentication: http://www.php-mysql-tutorial.com/user-authentication/image-verification.php // * Basic image authentication: http://www.weberdev.com/get_example-4067.html // * Also see veriword and freecap and PHP XSS attacks (http://www.hardened-php.net/advisory_012006.112.html) // // Changes: // This version now implements the following: // * 2006-05-05: // * Changed session_labels, etc. // * Added $sessionCode, $postCode initialization // * Initialized class vars in constructor using GLOBALS // * Used $_GET instead of $_SERVER to do check // * Increased num of max_attempts and numChars // // * 2006-05-03: Changed implementation to be in same file (at the end) // // * Logging of successful, bad_bot (brut force), and slip-through attempts // * Code to actually prevent slipthrough attacks (most effective anti-spam technique) // * Stops brute force attacks of re-using session across attempts or vhosts // // TODO: // * // ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // // Changes: // // 2005-08-07: Last time of update/change before better time accounting // Made my own changes to this... messed with class to make it less stringent // Added ?php everywhere // Need img.phtml?size=5 to create the image // Use the image in form.phtml and verify in verify.phtml // Combine form.phtml and verify.phtml into one file: combined.phtml; added $code_submitted // // 2006-04-30: Added brute force attack countering based on // freecap.php (basically, if they try to use the session's stored // code to keep making spam entries, we instead reset the session // after $max_attempts and then also log the attempt in the // /tmp/bad_bot.log file // // 2006-05-01: Added logging successful, bad_bot, and slip-through // (most vicious/effective) attempts. Also added code to actually // catch and prevent slipthrough attacks (BEST anti-spam technique). // ////////////////////////////////////////////////////////////////////// class vImage { ////////////////////////////////////////////////////////////////////// // // Customizations -- make changes below (also set via globals, above) // ////////////////////////////////////////////////////////////////////// var $h = 30; // Height of Image: default 20; var $colBG = "188 220 231"; // Background colour setting var $colTxt = "60 150 120"; // Text colour setting (black->greenish blue) var $colBorder = "0 128 192"; // Border colour setting var $charx = 20; // Lateral space of each char var $numCirculos = 5; // Number of random circles ////////////////////////////////////////////////////////////////////// // // No need to make any changes below here // ////////////////////////////////////////////////////////////////////// var $logfile = "/tmp/bad_bot.log"; // Bad_Bot log file (brute force) var $doBadBotLogging = 0; // Boolean to see if we should log bad robot attempts (1 = Yes, 0 = No) var $numChars = 5; // Size of String: default 5 (in case a spambot tries to set size=0 parameter) var $max_attempts = 5; // Max number of allowed attempts (brute force) // (slip-through attacks are more deadly so leave this relatively big) var $session_label = 'xyzpldlkjoj_23ljks'; // Session Code Label (change for yourself!) var $post_label = 'ljs_lj231vuewo10_m'; // Post Code Label (change for yourself!) var $max_num_label = 'max_attempts'; // Maximum Attempts Label (change for yourself!) var $num_attempts = 0; // Number of attempts they've already tried (brute force) var $w = 20; // Width of the image (automatically calculated below) var $DEBUG = 5; // Are we DEBUG'ing? var $sessionCode = ''; // Initialize the php cookie's session Code storage variable var $postCode = ''; // Initialize the user's posted Code storage variable var $image_display_label = 'display_image'; // Set display image variable label var $image_size_label = 'size'; // Set image size variable label // Constructor; just starts the server-side cookie session function vImage(){ // Initialize local variables to globals: $this->logfile = $GLOBALS['logfile']; $this->doBadBotLogging = $GLOBALS['doBadBotLogging']; $this->numChars = $GLOBALS['numChars']; $this->max_attempts = $GLOBALS['max_attempts']; $this->session_label = $GLOBALS['session_label']; $this->post_label = $GLOBALS['post_label']; $this->max_num_label = $GLOBALS['max_num_label']; // Set the session to expire in 15 mins... //session_save_path("."); // Save cookies in local directory? session_cache_expire(15); session_start(); } // Generate the text (string passcode) // Gerar is to generate in Portugese function gerText($num){ // Check passed size of string to make sure it's bigger than min numChars if ( ($num != '') && ($num > $this->numChars) ) $this->numChars = $num; // To generate random strings $this->texto = $this->gerString(); // SET the code we just generated in their server-side cookie for later comparison $_SESSION["$this->session_label"] = $this->texto; } // Load the codes (postCode is what they guessed; sessionCode is what we set) function loadCodes() { $this->postCode = isset($_POST["$this->post_label"]) ? $_POST["$this->post_label"] : ""; $this->sessionCode = isset($_SESSION["$this->session_label"]) ? $_SESSION["$this->session_label"] : ""; } ////////////////////////////////////////////////////// // Check the code; return true or false // Also, counter slip-through attempts by spambots in here! // (slipthrough spammers use a blank sessionCode in fake sessionID) ////////////////////////////////////////////////////// function checkCode() { if (isset($this->postCode)) $this->loadCodes(); // Some spammers got through with a blank code! if (empty($this->sessionCode)) { // Log slip-through attempts... if ($this->DEBUG > 0) { $this->LogAttempt("slipthrough"); } return false; } if ($this->postCode == $this->sessionCode) { // 2006-06-13: prevent piggy-backing (session hijacking) on a successful post attack // RESET the code before returning on a successful post // Do this to prevent a piggy-backing attack $this->gerText($this->numChars); // Log successful attempts? if ($this->DEBUG > 3) { $this->LogAttempt("success"); } return true; } else { // Log unsuccessful attempts? if ($this->DEBUG > 3) { $this->LogAttempt("nomatch"); } // Counter against a potential brute force attack $this->CounterBruteForce(); return false; } } ////////////////////////////////////////////////////// // Counter Potential Brute Force Attacks: ////////////////////////////////////////////////////// function CounterBruteForce() { if(empty($_SESSION["$this->max_num_label"])) { $_SESSION["$this->max_num_label"] = 1; $this->num_attempts = 1; } else { $_SESSION["$this->max_num_label"]++; $this->num_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["$this->max_num_label"] > $this->max_attempts) { // RESET the code here immediately! $this->gerText($this->numChars); // depending on how rude you want to be :-) //ImageString($im,5,0,20,"bugger off you spamming bastards!",$red); // Possibly send em to bad_bot page? Or log to a file??? if ($this->DEBUG > 0) { $this->LogAttempt("bad_bot"); } } } } // Log Bad_Bot Attempt! function LogAttempt($kind) { if ( $this->doBadBotLogging ) { // Open the filehandle (append to end) $fh = fopen($this->logfile, 'a') or die ("Couldn't open $this->logfile\n"); fwrite($fh, "$kind | ". $_SERVER['REMOTE_ADDR'] ." | ". date("Y-m-d H:i:s") ." | ". $this->num_attempts ." | Session:". $this->sessionCode ." | Post:". $this->postCode ."\n"); fclose($fh); } } function showCodBox($mode=0,$extra=''){ $str = "post_label\" ".$extra." > "; if ($mode) echo $str; else return $str; } function showImage(){ $this->gerImage(); header("Content-type: image/png"); ImagePng($this->im); } function gerImage(){ // To calculate size to fit text $this->w = ($this->numChars*$this->charx) + 40; // 5px de cada lado, 4px por char // To Create img // $this->im = imagecreate($this->w, $this->h); $this->im = imagecreatefrompng("background.png"); //to draw deep edge and imagefill($this->im, 0, 0, $this->getColor($this->colBorder)); //imagefilledrectangle ( $this->im, 1, 1, ($this->w-2), ($this->h-2), $this->getColor($this->colBG) ); // #desenhar circulos (to draw circles?) for ($i=1;$i<=$this->numCirculos;$i++) { $randomcolor = imagecolorallocate ($this->im , rand(100,255), rand(100,255),rand(100,255)); imageellipse($this->im,rand(0,$this->w-10),rand(0,$this->h-3), rand(25,50),rand(20,40),$randomcolor); } //To write text $ident = 20; for ($i=0;$i<$this->numChars;$i++){ $char = substr($this->texto, $i, 1); $font = rand(3,5); $y = round(($this->h-15)/2); // want random colored characters too // if still not good enough - go to http://us3.php.net/manual/en/function.imageloadfont.php //$col = $this->getColor($this->colTxt); $col = imagecolorallocate ($this->im , rand(50,250), rand(120,200),rand(120,200)); if (($i%2) == 0){ imagechar ( $this->im, $font, $ident, $y, $char, $col ); }else{ imagechar ( $this->im, $font, $ident, $y, $char, $col ); } $ident = $ident+$this->charx; } } function getColor($var){ $rgb = explode(" ",$var); $col = imagecolorallocate ($this->im, $rgb[0], $rgb[1], $rgb[2]); return $col; } function gerString(){ rand(0,time()); $possible="abcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()-+:;<>{}~,.?"; $str = ""; while(strlen($str)<$this->numChars) { $str.=substr($possible,(rand()%(strlen($possible))),1); } $txt = $str; return $txt; } } // End vImage class /////////////// Image Authentication Globals /////////////// ///// Image Authentication Header $vImage = new vImage(); // Code submitted just tells us whether or not they've attempted to submit a code yet if (! isset( $code_submitted ) ) { $code_submitted = 0; } else { $vImage->loadCodes(); } // Are we being called to show the image? if ( !empty($_GET["$vImage->image_size_label"]) ) { $vImage->gerText($_GET["$vImage->image_size_label"]); $vImage->showImage(); exit(); } ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // // Start Guestbook Output // ////////////////////////////////////////////////////////////////////// /////////////// Parse The Input /////////////// // Get our variables IF there was a post: // 2007-06-13: Replaced $HTTP_POST_VARS with $_POST if ( !empty( $_POST ) ) { reset ($_POST); while (list ($key, $val) = each ($_POST)) { // After hacking attempt, make checkCode(); // Print the Blank Response Subroutines in case no name or comment: if (!$theCode || !$comment || !$name) { $code_submitted = 0; MissingInfo(); } // Actual Output of Comments/Entries Now (if not exited) WriteDataFile(); // Mail a copy of the comment to us if ($mail == '1') { SendMeMail(); } // Mail a copy of the comment to the signer if ($remote_mail == '1' && $email) { SendThemMail(); } // Print Out Initial Output Location Heading if ($redirection == '1') { // Redirect header header ("Location: $guestbookurl"); exit; // Make sure that code below does not get executed when we redirect. } else { No_Redirection(); } } // Keep fxns inline for ease of distribution //require ("guestbook-fxns.inc"); //////////////////// Start Output Functions //////////////////// // // // // Fxn to Verify Form Variables // function VerifyFormVariables() { // Declare my globals (so it doesn't just create a new, local var) global $FORM, $orig_comments, $comment, $guestbookurl; global $date, $shortdate, $cgiurl, $uselog, $guestlog; global $defaultpassword; // 2007-01-31: Thanks to Curran Nachbar Schiefelbein global $allow_html, $line_breaks; // 2008-02-07: using it but not added in! // 2007-06-13: added isset() check if ( isset($FORM{'url'}) && preg_match("%^http://$%",$FORM{'url'}) ) { $FORM{'url'} = ""; } // Fix the $referrername variable's sentence terminator: // 2007-06-13: added isset() check if ( isset($FORM{'referrername'}) && preg_match("%[^\!]$%",$FORM{'referrername'})) { $FORM{'referrername'} .= "."; } // Should we allow html tags? if (isset($allow_html) && $allow_html == 0) { // Could replace all < & > with > & < instead (to keep context?) $FORM{'comments'} = preg_replace("%<([^>]|\n)*>%", "", $FORM{'comments'}); } /* // Replace all remaining < and > characters if (isset($allow_html) && $allow_html == 0) { $FORM{'comments'} = preg_replace("%<%", "<", $FORM{'comments'}); $FORM{'comments'} = preg_replace("%>%", ">", $FORM{'comments'}); } */ if (isset($line_breaks) && $line_breaks == 1) { $FORM{'comments'} = preg_replace("%\cM\n/\n%g","",$FORM{'comments'}); } if ( isset( $FORM{'private'} ) ) { if (!$FORM{'password'}) { $FORM{'password'} = $defaultpassword; } // Parse comment for weird characters: // Parse for quotes (all varieties) and replace with \' // Strange, had to separate these out because of multiple \\'s in comment // Order is important $FORM{'comments'} = preg_replace("%['\"]%", "\\'", $FORM{'comments'}); $FORM{'comments'} = preg_replace("%[\`]%", "\\'", $FORM{'comments'}); // Parse html out of private comments by default $FORM{'comments'} = preg_replace("%<([^>]|\n)*>%", "", $FORM{'comments'}); // Parse for ^M's (the \r\n that Windows doesn't understand) $FORM{'comments'} = preg_replace("%\r?\n%", "\\n", $FORM{'comments'}); $orig_comments = $FORM{'comments'}; $password = $FORM{'password'}; $comment = <<Private Guestbook Comment PRIVATECOMMENT ; // Hose trailing newline from above <", $FORM{'comments'}); $orig_comments = $FORM{'comments'}; $comment = $FORM{'comments'}; } } // // Fxn to show missing information: // function MissingInfo() { // Declare my globals (so it doesn't just create a new, local var) global $name, $email, $url, $city, $state, $country, $referrer, $comment, $private, $password, $ip; global $date, $shortdate, $uselog, $bcol; global $guestbookurl, $cgiurl, $homeurl, $homepageText; global $vImage; // What's missing? if (!$comment) { $missingitem = "The Comments Section"; } elseif (!$name) { $missingitem = "Your Name"; ////////////////////////////////////////////////////////////////////// // 2006-07-01: This HAS to be the LAST CHECKED item otherwise it'll // always report that incorrect authentication code was // entered since we re-set authentication code even on // a successful submission (which was done to prevent // piggy-back/session hijacking attacks by spambots). ////////////////////////////////////////////////////////////////////// } elseif (!$vImage->checkCode()) { $missingitem = "Correct Authentication Code"; } // Print the form: print << $missingitem wasn't entered $missingitem wasn't entered! How could you forget to enter $missingitem??? Are you taking drugs?? Do I have to give you that lecture again?? Try it again... and for Pete's sake, get it right this time, willya?!?! Enter Info Below: Fill in the blanks to add to our guestbook. Please enter at least your name along with the correct authentication code and your comment(s)... Thanks! MISSINGINFO ; // Print the actual form now $foo = $bcol; $bcol = "D0D0D0"; PrintAddForm(); $bcol = $foo; print << * Back to the Guestbook Entries * Back to $homepageText
TITLEBODY ; // Set addForm bgcolor: $bcol = "#CCCCCC"; // Set the default background colour: if (! isset( $_GET['bg'] ) ) { $bg="E0E0E0"; } else { $bg=$_GET['bg']; } // Make bgcolour array: $bgcolours = array ( "E0E0E0", "AntiqueWhite", "DarkTurquoise", "LightGrey", "MediumAquamarine", "SeaShell", "Silver" ); // Set default format: if (! isset( $_GET['f'] ) ) { $f="Elegant"; } else { $f=$_GET['f']; } // Make format array: $formats = array ( "Elegant", "Simple", "Standard" ); // $number is number of entries per page if (! isset( $_GET['number'] ) ) { $number = 50; } else { $number=$_GET['number']; } ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // GuestBook Appearance Template (editing not recommended) // (stuff that will go out into template files later) // ///// Title & Header function PrintTitleHeader() { // Declare my globals (so it doesn't just create a new, local var) global $titleTag, $titleText, $transpgif; print << $titleTag $titleText TITLEHEADER ; } ///// Top navigational table function PrintTopNav() { // Declare my globals (so it doesn't just create a new, local var) global $number, $no_entries, $bg, $f, $cgiurl; print << First $number entries All $no_entries entries TOPNAV ; } ///// The Add Form function PrintAddForm() { // Declare my globals (so it doesn't just create a new, local var) global $postGuestbook, $bcol, $transpgif, $pwidth, $referrer_opt; global $name, $email, $url, $city, $state, $country, $referrer, $orig_comments, $comment, $private, $password, $ip; global $vImage, $code_submitted, $myName; if (!$url) {$url = "http://";} if (!$country) {$country = "USA";} print << Please enter at least your name along with the correct authentication code and your comment(s)... Thanks! Your Name: E-mail: Homepage URL: Geographical Info: City, State, Country , , How did you stumble into our Virtual Home? $referrer_opt Comments: $orig_comments Make it private? If private, please specify password (case sensitive) Sorry, can't use HTML in a private comment. Also, if no password is specified, default password will be assigned. Please enter the code you see above: ADDFORM2 ; $vImage->showCodBox(1); print <<(Or hit REFRESH if it's broken) * ADDFORM3 ; } ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // // No need to make any changes below here // ////////////////////////////////////////////////////////////////////// /////////////// Set Some Variables /////////////// // 2007-06-13: For PHP5, set the timezone: date_default_timezone_set("America/Los_Angeles"); // Figure out the date (e.g., Tuesday, June 1, 1999 at 7:11 PM and 6/4/1999 17:42) $dayOfWeek = date("l"); $monthOfYear = date("F"); $thisMonth = date("n"); $thisDay = date("j"); $yearNumber = date("Y"); $mytime = date("g:i A"); $thisHour = date("G"); $thisMinute = date("i"); $myLongDate = "$dayOfWeek, $monthOfYear $thisDay, $yearNumber at $mytime"; $myShortDate = "$thisMonth/$thisDay/$yearNumber $thisHour:$thisMinute"; // Get the Date for Entry (legacy code) $date = $myLongDate; $shortdate = $myShortDate; $myName = $_SERVER['PHP_SELF']; // result: /guestbook/index.php ////////////////////////////////////////////////////////////////////// // // Library: authenticate_image.phtml // Version: 0.3.8 // Used in http://www.sethi.org/guestbook/ // Implemented: Implemented in same file (at the end) // // Description: This class generates an image with random text that // can be used as part of a CAPTCHA (see below) system and used as image // verification for forms of any sort (this was developed in the context // of The Sethi Family GuestBook. It incorporates elements designed to // foil bots by confusing OCR software and also uses strong PHP session // security in order to disallow them getting around it (e.g., preventing // brute force attacks, slip-through attempts, etc.). // Also, disallows blank sessionIDs as a great anti-spam technique for guestbooks, // etc. to use against spambots that use PHP's vulnerability, a la // sending fake (/guestbook/?PHPSESSID=e295d8b99fb4a8b38bd496373391b803&number=724) // requests with blank sessionIDs. // // License: // GPL & Postcard-Ware! If you like this program and // use it, drop me a postcard or an email or just sign // our guestbook and tell me how great I am. :) And // hey, if you *really* like it, don't worry about // designing that shrine dedicated to my greatness... a // simple link back to our homepage should suffice. // // **************************************************** // Also, if you do use this script, please forward your // guestbook and homepage URLs to me at rickys@sethi.org // as I'll be compiling a page of users of the script // from around the world (should help pump some traffic // your way, too). // **************************************************** // // Credits: // Original idea based on Rafael Machado Dohms' vImage in Portugese // http://planeta.terra.com.br/informatica/d2000/vImage/vImage_withexamples.zip // // Author: // Ricky J. Sethi (rickys@sethi.org) // http://www.sethi.org/ // // References: Some links and references for CAPTCHA (image verification): // * CAPTCHA decoder: http://sam.zoy.org/pwntcha/ // * Basic CAPTCHA: http://captchas.net/sample/php/ // * Basic image authentication: http://www.php-mysql-tutorial.com/user-authentication/image-verification.php // * Basic image authentication: http://www.weberdev.com/get_example-4067.html // * Also see veriword and freecap and PHP XSS attacks (http://www.hardened-php.net/advisory_012006.112.html) // // Changes: // This version now implements the following: // * 2006-05-05: // * Changed session_labels, etc. // * Added $sessionCode, $postCode initialization // * Initialized class vars in constructor using GLOBALS // * Used $_GET instead of $_SERVER to do check // * Increased num of max_attempts and numChars // // * 2006-05-03: Changed implementation to be in same file (at the end) // // * Logging of successful, bad_bot (brut force), and slip-through attempts // * Code to actually prevent slipthrough attacks (most effective anti-spam technique) // * Stops brute force attacks of re-using session across attempts or vhosts // // TODO: // * // ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // // Changes: // // 2005-08-07: Last time of update/change before better time accounting // Made my own changes to this... messed with class to make it less stringent // Added ?php everywhere // Need img.phtml?size=5 to create the image // Use the image in form.phtml and verify in verify.phtml // Combine form.phtml and verify.phtml into one file: combined.phtml; added $code_submitted // // 2006-04-30: Added brute force attack countering based on // freecap.php (basically, if they try to use the session's stored // code to keep making spam entries, we instead reset the session // after $max_attempts and then also log the attempt in the // /tmp/bad_bot.log file // // 2006-05-01: Added logging successful, bad_bot, and slip-through // (most vicious/effective) attempts. Also added code to actually // catch and prevent slipthrough attacks (BEST anti-spam technique). // ////////////////////////////////////////////////////////////////////// class vImage { ////////////////////////////////////////////////////////////////////// // // Customizations -- make changes below (also set via globals, above) // ////////////////////////////////////////////////////////////////////// var $h = 30; // Height of Image: default 20; var $colBG = "188 220 231"; // Background colour setting var $colTxt = "60 150 120"; // Text colour setting (black->greenish blue) var $colBorder = "0 128 192"; // Border colour setting var $charx = 20; // Lateral space of each char var $numCirculos = 5; // Number of random circles ////////////////////////////////////////////////////////////////////// // // No need to make any changes below here // ////////////////////////////////////////////////////////////////////// var $logfile = "/tmp/bad_bot.log"; // Bad_Bot log file (brute force) var $doBadBotLogging = 0; // Boolean to see if we should log bad robot attempts (1 = Yes, 0 = No) var $numChars = 5; // Size of String: default 5 (in case a spambot tries to set size=0 parameter) var $max_attempts = 5; // Max number of allowed attempts (brute force) // (slip-through attacks are more deadly so leave this relatively big) var $session_label = 'xyzpldlkjoj_23ljks'; // Session Code Label (change for yourself!) var $post_label = 'ljs_lj231vuewo10_m'; // Post Code Label (change for yourself!) var $max_num_label = 'max_attempts'; // Maximum Attempts Label (change for yourself!) var $num_attempts = 0; // Number of attempts they've already tried (brute force) var $w = 20; // Width of the image (automatically calculated below) var $DEBUG = 5; // Are we DEBUG'ing? var $sessionCode = ''; // Initialize the php cookie's session Code storage variable var $postCode = ''; // Initialize the user's posted Code storage variable var $image_display_label = 'display_image'; // Set display image variable label var $image_size_label = 'size'; // Set image size variable label // Constructor; just starts the server-side cookie session function vImage(){ // Initialize local variables to globals: $this->logfile = $GLOBALS['logfile']; $this->doBadBotLogging = $GLOBALS['doBadBotLogging']; $this->numChars = $GLOBALS['numChars']; $this->max_attempts = $GLOBALS['max_attempts']; $this->session_label = $GLOBALS['session_label']; $this->post_label = $GLOBALS['post_label']; $this->max_num_label = $GLOBALS['max_num_label']; // Set the session to expire in 15 mins... //session_save_path("."); // Save cookies in local directory? session_cache_expire(15); session_start(); } // Generate the text (string passcode) // Gerar is to generate in Portugese function gerText($num){ // Check passed size of string to make sure it's bigger than min numChars if ( ($num != '') && ($num > $this->numChars) ) $this->numChars = $num; // To generate random strings $this->texto = $this->gerString(); // SET the code we just generated in their server-side cookie for later comparison $_SESSION["$this->session_label"] = $this->texto; } // Load the codes (postCode is what they guessed; sessionCode is what we set) function loadCodes() { $this->postCode = isset($_POST["$this->post_label"]) ? $_POST["$this->post_label"] : ""; $this->sessionCode = isset($_SESSION["$this->session_label"]) ? $_SESSION["$this->session_label"] : ""; } ////////////////////////////////////////////////////// // Check the code; return true or false // Also, counter slip-through attempts by spambots in here! // (slipthrough spammers use a blank sessionCode in fake sessionID) ////////////////////////////////////////////////////// function checkCode() { if (isset($this->postCode)) $this->loadCodes(); // Some spammers got through with a blank code! if (empty($this->sessionCode)) { // Log slip-through attempts... if ($this->DEBUG > 0) { $this->LogAttempt("slipthrough"); } return false; } if ($this->postCode == $this->sessionCode) { // 2006-06-13: prevent piggy-backing (session hijacking) on a successful post attack // RESET the code before returning on a successful post // Do this to prevent a piggy-backing attack $this->gerText($this->numChars); // Log successful attempts? if ($this->DEBUG > 3) { $this->LogAttempt("success"); } return true; } else { // Log unsuccessful attempts? if ($this->DEBUG > 3) { $this->LogAttempt("nomatch"); } // Counter against a potential brute force attack $this->CounterBruteForce(); return false; } } ////////////////////////////////////////////////////// // Counter Potential Brute Force Attacks: ////////////////////////////////////////////////////// function CounterBruteForce() { if(empty($_SESSION["$this->max_num_label"])) { $_SESSION["$this->max_num_label"] = 1; $this->num_attempts = 1; } else { $_SESSION["$this->max_num_label"]++; $this->num_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["$this->max_num_label"] > $this->max_attempts) { // RESET the code here immediately! $this->gerText($this->numChars); // depending on how rude you want to be :-) //ImageString($im,5,0,20,"bugger off you spamming bastards!",$red); // Possibly send em to bad_bot page? Or log to a file??? if ($this->DEBUG > 0) { $this->LogAttempt("bad_bot"); } } } } // Log Bad_Bot Attempt! function LogAttempt($kind) { if ( $this->doBadBotLogging ) { // Open the filehandle (append to end) $fh = fopen($this->logfile, 'a') or die ("Couldn't open $this->logfile\n"); fwrite($fh, "$kind | ". $_SERVER['REMOTE_ADDR'] ." | ". date("Y-m-d H:i:s") ." | ". $this->num_attempts ." | Session:". $this->sessionCode ." | Post:". $this->postCode ."\n"); fclose($fh); } } function showCodBox($mode=0,$extra=''){ $str = "post_label\" ".$extra." > "; if ($mode) echo $str; else return $str; } function showImage(){ $this->gerImage(); header("Content-type: image/png"); ImagePng($this->im); } function gerImage(){ // To calculate size to fit text $this->w = ($this->numChars*$this->charx) + 40; // 5px de cada lado, 4px por char // To Create img // $this->im = imagecreate($this->w, $this->h); $this->im = imagecreatefrompng("background.png"); //to draw deep edge and imagefill($this->im, 0, 0, $this->getColor($this->colBorder)); //imagefilledrectangle ( $this->im, 1, 1, ($this->w-2), ($this->h-2), $this->getColor($this->colBG) ); // #desenhar circulos (to draw circles?) for ($i=1;$i<=$this->numCirculos;$i++) { $randomcolor = imagecolorallocate ($this->im , rand(100,255), rand(100,255),rand(100,255)); imageellipse($this->im,rand(0,$this->w-10),rand(0,$this->h-3), rand(25,50),rand(20,40),$randomcolor); } //To write text $ident = 20; for ($i=0;$i<$this->numChars;$i++){ $char = substr($this->texto, $i, 1); $font = rand(3,5); $y = round(($this->h-15)/2); // want random colored characters too // if still not good enough - go to http://us3.php.net/manual/en/function.imageloadfont.php //$col = $this->getColor($this->colTxt); $col = imagecolorallocate ($this->im , rand(50,250), rand(120,200),rand(120,200)); if (($i%2) == 0){ imagechar ( $this->im, $font, $ident, $y, $char, $col ); }else{ imagechar ( $this->im, $font, $ident, $y, $char, $col ); } $ident = $ident+$this->charx; } } function getColor($var){ $rgb = explode(" ",$var); $col = imagecolorallocate ($this->im, $rgb[0], $rgb[1], $rgb[2]); return $col; } function gerString(){ rand(0,time()); $possible="abcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()-+:;<>{}~,.?"; $str = ""; while(strlen($str)<$this->numChars) { $str.=substr($possible,(rand()%(strlen($possible))),1); } $txt = $str; return $txt; } } // End vImage class /////////////// Image Authentication Globals /////////////// ///// Image Authentication Header $vImage = new vImage(); // Code submitted just tells us whether or not they've attempted to submit a code yet if (! isset( $code_submitted ) ) { $code_submitted = 0; } else { $vImage->loadCodes(); } // Are we being called to show the image? if ( !empty($_GET["$vImage->image_size_label"]) ) { $vImage->gerText($_GET["$vImage->image_size_label"]); $vImage->showImage(); exit(); } ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // // Start Guestbook Output // ////////////////////////////////////////////////////////////////////// /////////////// Parse The Input /////////////// // Get our variables IF there was a post: // 2007-06-13: Replaced $HTTP_POST_VARS with $_POST if ( !empty( $_POST ) ) { reset ($_POST); while (list ($key, $val) = each ($_POST)) { // After hacking attempt, make checkCode(); // Print the Blank Response Subroutines in case no name or comment: if (!$theCode || !$comment || !$name) { $code_submitted = 0; MissingInfo(); } // Actual Output of Comments/Entries Now (if not exited) WriteDataFile(); // Mail a copy of the comment to us if ($mail == '1') { SendMeMail(); } // Mail a copy of the comment to the signer if ($remote_mail == '1' && $email) { SendThemMail(); } // Print Out Initial Output Location Heading if ($redirection == '1') { // Redirect header header ("Location: $guestbookurl"); exit; // Make sure that code below does not get executed when we redirect. } else { No_Redirection(); } } // Keep fxns inline for ease of distribution //require ("guestbook-fxns.inc"); //////////////////// Start Output Functions //////////////////// // // // // Fxn to Verify Form Variables // function VerifyFormVariables() { // Declare my globals (so it doesn't just create a new, local var) global $FORM, $orig_comments, $comment, $guestbookurl; global $date, $shortdate, $cgiurl, $uselog, $guestlog; global $defaultpassword; // 2007-01-31: Thanks to Curran Nachbar Schiefelbein global $allow_html, $line_breaks; // 2008-02-07: using it but not added in! // 2007-06-13: added isset() check if ( isset($FORM{'url'}) && preg_match("%^http://$%",$FORM{'url'}) ) { $FORM{'url'} = ""; } // Fix the $referrername variable's sentence terminator: // 2007-06-13: added isset() check if ( isset($FORM{'referrername'}) && preg_match("%[^\!]$%",$FORM{'referrername'})) { $FORM{'referrername'} .= "."; } // Should we allow html tags? if (isset($allow_html) && $allow_html == 0) { // Could replace all < & > with > & < instead (to keep context?) $FORM{'comments'} = preg_replace("%<([^>]|\n)*>%", "", $FORM{'comments'}); } /* // Replace all remaining < and > characters if (isset($allow_html) && $allow_html == 0) { $FORM{'comments'} = preg_replace("%<%", "<", $FORM{'comments'}); $FORM{'comments'} = preg_replace("%>%", ">", $FORM{'comments'}); } */ if (isset($line_breaks) && $line_breaks == 1) { $FORM{'comments'} = preg_replace("%\cM\n/\n%g","",$FORM{'comments'}); } if ( isset( $FORM{'private'} ) ) { if (!$FORM{'password'}) { $FORM{'password'} = $defaultpassword; } // Parse comment for weird characters: // Parse for quotes (all varieties) and replace with \' // Strange, had to separate these out because of multiple \\'s in comment // Order is important $FORM{'comments'} = preg_replace("%['\"]%", "\\'", $FORM{'comments'}); $FORM{'comments'} = preg_replace("%[\`]%", "\\'", $FORM{'comments'}); // Parse html out of private comments by default $FORM{'comments'} = preg_replace("%<([^>]|\n)*>%", "", $FORM{'comments'}); // Parse for ^M's (the \r\n that Windows doesn't understand) $FORM{'comments'} = preg_replace("%\r?\n%", "\\n", $FORM{'comments'}); $orig_comments = $FORM{'comments'}; $password = $FORM{'password'}; $comment = <<Private Guestbook Comment PRIVATECOMMENT ; // Hose trailing newline from above <", $FORM{'comments'}); $orig_comments = $FORM{'comments'}; $comment = $FORM{'comments'}; } } // // Fxn to show missing information: // function MissingInfo() { // Declare my globals (so it doesn't just create a new, local var) global $name, $email, $url, $city, $state, $country, $referrer, $comment, $private, $password, $ip; global $date, $shortdate, $uselog, $bcol; global $guestbookurl, $cgiurl, $homeurl, $homepageText; global $vImage; // What's missing? if (!$comment) { $missingitem = "The Comments Section"; } elseif (!$name) { $missingitem = "Your Name"; ////////////////////////////////////////////////////////////////////// // 2006-07-01: This HAS to be the LAST CHECKED item otherwise it'll // always report that incorrect authentication code was // entered since we re-set authentication code even on // a successful submission (which was done to prevent // piggy-back/session hijacking attacks by spambots). ////////////////////////////////////////////////////////////////////// } elseif (!$vImage->checkCode()) { $missingitem = "Correct Authentication Code"; } // Print the form: print << $missingitem wasn't entered $missingitem wasn't entered! How could you forget to enter $missingitem??? Are you taking drugs?? Do I have to give you that lecture again?? Try it again... and for Pete's sake, get it right this time, willya?!?! Enter Info Below: Fill in the blanks to add to our guestbook. Please enter at least your name along with the correct authentication code and your comment(s)... Thanks! MISSINGINFO ; // Print the actual form now $foo = $bcol; $bcol = "D0D0D0"; PrintAddForm(); $bcol = $foo; print << * Back to the Guestbook Entries * Back to $homepageText
", $FORM{'comments'}); $orig_comments = $FORM{'comments'}; $comment = $FORM{'comments'}; } } // // Fxn to show missing information: // function MissingInfo() { // Declare my globals (so it doesn't just create a new, local var) global $name, $email, $url, $city, $state, $country, $referrer, $comment, $private, $password, $ip; global $date, $shortdate, $uselog, $bcol; global $guestbookurl, $cgiurl, $homeurl, $homepageText; global $vImage; // What's missing? if (!$comment) { $missingitem = "The Comments Section"; } elseif (!$name) { $missingitem = "Your Name"; ////////////////////////////////////////////////////////////////////// // 2006-07-01: This HAS to be the LAST CHECKED item otherwise it'll // always report that incorrect authentication code was // entered since we re-set authentication code even on // a successful submission (which was done to prevent // piggy-back/session hijacking attacks by spambots). ////////////////////////////////////////////////////////////////////// } elseif (!$vImage->checkCode()) { $missingitem = "Correct Authentication Code"; } // Print the form: print << $missingitem wasn't entered $missingitem wasn't entered! How could you forget to enter $missingitem??? Are you taking drugs?? Do I have to give you that lecture again?? Try it again... and for Pete's sake, get it right this time, willya?!?! Enter Info Below: Fill in the blanks to add to our guestbook. Please enter at least your name along with the correct authentication code and your comment(s)... Thanks! MISSINGINFO ; // Print the actual form now $foo = $bcol; $bcol = "D0D0D0"; PrintAddForm(); $bcol = $foo; print << * Back to the Guestbook Entries * Back to $homepageText