You are not enjoying the benefits offered by registering. To register click here...
  
Forums | Prefs | Your Messages | Members | Recent | Search
Quick Search


Advanced Search
| Calendar | Doc | Dev' Blog! |
Chat (Alpha Version)

Guest, do not forget to login ( Register )
 Top > Forums > Other > Downloads
 Future Captcha?

Skin Selection:
 (1 pages) 1  
chris

The Culprit
Group: Admins
Posts: 1,516
Reputation: 38


Posted on Apr 15, 2006, 3:47 am by chris
Future Captcha?
Update (04/16)

Thanks to Ryan, I have thought about the problem some more and here is a new version, entirely pictures-based.

The Idea

After visiting KittenAuth, and reading some of the comments made by Bruce Schneier and his readers here, I've decided to give it a try myself.

Actually, if I get it right, I could add it in the NextBBS Source Tree.

Two comments I particularly felt like addressing were:
  • Clicking 4 pictures out of 9 would be more challenging

  • The test can be defeated by teaching the computer to identify the kitten pictures.
    Ryan FB posted a very compelling comment, explaining how he is still ahead of Oli's program using the PCA-SIFT algorithm. Take a look at this complete post.
    Deapesh Misra summed it up as follow: "The database requirement of a CAPTCHA needs that the database is public and large".


I decided to take advantage of Flickr's open and well-documented API to grab as many themes and pictures as one may need.
I took two shortcuts:
  • Rather than reinvent the wheel, I decided to use phpFlickr (this package can do just about anything!)
  • Rather shamefully, I also reused Oli''s Javascript for the pictures selection part.


Demo Page

Click this link and see how much you hate this Captcha.

Suggestions are welcome. I know, that page can be frustrating at times, there is room for improvement, hey', it's just a proof of concept!

The Source Code

php Code:
  1. <?php
  2. set_include_path('phpFlickr/PEAR' . PATH_SEPARATOR . get_include_path());
  3.  
  4. define('MODE_KEYWORDS', 0x01);
  5. define('MODE_ALL_PICS', 0x02);
  6. define('SORRY', "This seems to happen a lot when querying Flickr. I have to figure this one out. Very sorry.");
  7. define('FOOTER', "<br /><a href='".$_SERVER['PHP_SELF']."?mode=tags'>Take this Test with Words (Tags)</a> --- <a href='".$_SERVER['PHP_SELF']."?mode=pics'>Take this Test with Pictures Only</a><br /><br /><strong><i><a href='http://www.nextbbs.com/do_topic_id_573_post_3927'>To My Post & Source Code</i></strong></a>");
  8. define('MAX_RETRIES', '10');
  9. define('DB_TABLE', 'cfr_challenges');
  10. // ++ Modify these two lines
  11. define('DB_DSN', 'mysql://labs:labs@localhost/labs');
  12. define('FLICKR_KEY', '749b450d27abbffe315c244d49dc6cac');
  13. // --
  14.  
  15. class Captcha
  16. {
  17. var $debug, $mode, $seedtag, $childtag, $credits, $f;
  18.  
  19. function Captcha()
  20. {
  21. // Change this line to switch modes
  22. $this->mode = MODE_ALL_PICS;
  23. if(isset($_GET['mode']) && $_GET['mode']=='tags')
  24. $this->mode = MODE_KEYWORDS;
  25. $this->debug = 2;
  26. $this->credits = array();
  27.  
  28. $this->challenge();
  29. }
  30.  
  31. function log($txt)
  32. {
  33. if($this->debug>0)
  34. {
  35. print $txt;
  36. if($this->debug>1)
  37. {
  38. $f = fopen("/tmp/captcha.log", "a");
  39. fwrite($f, $txt." ");
  40. fclose($f);
  41. }
  42. }
  43. }
  44.  
  45. function challenge()
  46. {
  47. // Did they submit?
  48. if(isset($_POST['hiddenid']) && isset($_POST['hiddenf']))
  49. {
  50. $this->processResponse(&$_POST['hiddenid'], &$_POST['hiddenf']);
  51. }
  52. else
  53. {
  54. $this->askQuestion();
  55. }
  56. }
  57.  
  58. function processResponse($id, $reply)
  59. {
  60. require_once 'DB.php';
  61. $db =& DB::connect(DB_DSN);
  62. if (PEAR::isError($db)) {
  63. die($db->getMessage());
  64. }
  65. $res = $db->query("SELECT valid FROM `".DB_TABLE."` WHERE `cid`='".$id."'");
  66. $row = $res->fetchRow(DB_FETCHMODE_ORDERED);
  67. $valid = $row[0];
  68. $res->free();
  69. // Get rid of this challenge
  70. $db->query("DELETE FROM `".DB_TABLE."` WHERE `cid`='".$id."'");
  71.  
  72. // Sort reply
  73. $tmp = array();
  74. for($i=0; $i<strlen($reply); $i++)
  75. $tmp[] = $reply[$i];
  76. sort($tmp);
  77. $sortedreply = '';
  78. foreach($tmp as $c)
  79. $sortedreply .= $c;
  80. //
  81.  
  82. if($sortedreply == $valid)
  83. print "<h1>Congratulations! You passed. You must be a human.</h1>";
  84. else
  85. print "<h3><div style='color:red;'>Oh, I'm sorry, this is not the correct answer.</div></h3>";
  86.  
  87. print FOOTER."</div>";
  88. }
  89.  
  90. function _getPictures($args)
  91. {
  92. $bRandom = &$args['random'];
  93. $nKeep = &$args['keep'];
  94. $nMaxSize = &$args['maxSize'];
  95.  
  96. for($ctr=0; $ctr<MAX_RETRIES; $ctr++)
  97. {
  98. if($bRandom)
  99. {
  100. $sortOrders = array(
  101. 'date_posted_desc',
  102. 'date_taken_desc',
  103. 'interestingness_desc');
  104.  
  105. $sortOrderIdx = rand(0, 2);
  106. $p_sort = $sortOrders[$sortOrderIdx];
  107.  
  108. $takenOrUpload = rand(0, 1);
  109. $p_maxDate = $takenOrUpload == 1 ? 'max_taken_date' : 'max_upload_date';
  110. $aWhileBack = time() - 182 * 24 * 60 * 60;
  111. $stopAt = rand($aWhileBack, time());
  112. $p_maxDateValue = date('Y-m-d H:i:s', $stopAt);
  113.  
  114. $args =
  115. 'license' => '4', // Attribution License
  116. 'per_page' => $nMaxSize,
  117. 'sort' => $p_sort,
  118. $p_maxDate => $p_maxDateValue,
  119. );
  120. }
  121. else
  122. {
  123. $seedtags = array(
  124. 'cat', 'cats', 'baby', 'beach', 'dog', 'dogs', 'flower', 'flowers', 'food', 'garden', 'girl', 'graffiti', 'house',
  125. 'kids', 'lake', 'mountain', 'mountains', 'park', 'rock', 'sign', 'turtle', 'snow', 'sun', 'sunset', 'tree', 'trees',
  126. 'wedding', 'zoo',
  127. );
  128.  
  129. $l = count($seedtags) - 1;
  130. $idx = rand(0, $l);
  131. $this->seedtag = $seedtags[$idx];
  132. $this->log("Using Tag: ".$this->seedtag."<br />");
  133.  
  134. $this->log( "Related tags:<br> ");
  135. $comma='';
  136.  
  137. $tags = $this->f->tags_getRelated($this->seedtag);
  138. foreach ($tags['tag'] as $tag)
  139. {
  140. $this->log($comma.$tag);
  141. $comma = ',';
  142. }
  143. $l = count($tags['tag']);
  144. $idx = rand(0, $l) - 1;
  145. $this->childtag = $tags['tag'][$idx];
  146. $this->log( "Retrieving Photos with tag '".$this->seedtag."' and child tag '".$this->childtag."':<br> ");
  147.  
  148. $args =
  149. "license" => '4', // Attribution License
  150. 'per_page' => $nMaxSize,
  151. 'tag_mode' => 'all',
  152. 'tags' =>$this->seedtag.','.$this->childtag,
  153. );
  154. }
  155.  
  156. $photos = $this->f->photos_search($args);
  157. if(count($photos['photo']) < $nMaxSize)
  158. {
  159. $this->log("Error retrieving info from Flicker...retrying<br />");
  160. usleep(500000);
  161. }
  162. else
  163. break;
  164. }
  165.  
  166. if(count($photos['photo']) < $nMaxSize)
  167. die("</div><a href='".$_SERVER['PHP_SELF']."'>Not enough sample images".SORRY." Click to try again.</a>");
  168.  
  169. $photosArray = array();
  170. while(count($photosArray)<$nKeep)
  171. {
  172. $r = rand(0, $nMaxSize - 1);
  173. if(!isset($photosArray[$r]))
  174. $photosArray[$r] = true;
  175. }
  176.  
  177. if($this->mode==MODE_ALL_PICS && !$bRandom)
  178. {
  179. $queryArray = array();
  180. while(count($queryArray)<$nKeep)
  181. {
  182. $r = rand(0, $nMaxSize - 1);
  183. if(!isset($photosArray[$r]) && !isset($queryArray[$r]))
  184. $queryArray[$r] = true;
  185. }
  186. }
  187.  
  188. $this->log("Keeping only $nKeep pictures<br />");
  189.  
  190. $runner = 0;
  191. foreach ($photos['photo'] as $photo)
  192. {
  193. // Build image and link tags for each photo
  194. if(isset($photosArray[$runner]))
  195. {
  196. $photosArray[$runner] = array('valid'=>($bRandom?false:true), 'link'=>$this->f->buildPhotoURL($photo, "Square"));
  197. $this->credits .=
  198. "<a href='http://www.flickr.com/photos/$photo[owner]/$photo[id]'>" .
  199. $photo[title] .
  200. "</a><br />";
  201. # $this->log($samplePhotos[$runner]);
  202. }
  203. else if($this->mode==MODE_ALL_PICS && isset($queryArray[$runner]))
  204. {
  205. $queryArray[$runner] = array('link'=>$this->f->buildPhotoURL($photo, "Square"));
  206. $credits .=
  207. "<a href='http://www.flickr.com/photos/$photo[owner]/$photo[id]'>" .
  208. $photo[title] .
  209. "</a><br />";
  210. }
  211. $runner ++;
  212. }
  213.  
  214. if($this->mode==MODE_ALL_PICS && !$bRandom)
  215. return array($photosArray, $queryArray);
  216. else
  217. return $photosArray;
  218. }
  219.  
  220. function _getDecoy($prefix, $idx, $link)
  221. {
  222. // Retrieve picture from Flickr
  223. $this->f->req->setMethod(HTTP_REQUEST_METHOD_GET);
  224. $this->f->req->setURL($link);
  225. if(!$this->f->req->sendRequest())
  226. {
  227. print "Error while retrieving picture from Flickr. Time out?<br /><br />";
  228. return null;
  229. }
  230. $response = $this->f->req->getResponseBody();
  231. $fname = 'tmp/'.$prefix.$idx.'.jpg';
  232. $h = fopen($fname, "wb");
  233. fwrite($h, $response);
  234. fclose($h);
  235. return $fname;
  236. }
  237.  
  238. function askQuestion()
  239. {
  240. require_once("phpFlickr/phpFlickr.php");
  241. // Create new phpFlickr object
  242. $this->f = new phpFlickr(FLICKR_KEY);
  243.  
  244. // 1st, discover 50 random pictures
  245. $maxSample = 50;
  246. $maxValid = 50;
  247.  
  248. print <<<EOB
  249. <HTML>
  250. <BODY>
  251. <form id='submitform' action='{$_SERVER['PHP_SELF']}' method='post'>
  252. <input type="hidden" id="hiddenf" name="hiddenf" value="" />
  253. <div id="kitty">
  254. <style type="text/css">
  255. #kitty {
  256. text-align:center;
  257. }
  258. table.kittyauth {
  259. margin:auto;
  260. border: none;
  261. background-color:#000;<