⬆️ ⬇️

Custom CAPTCHA

About creating captcha for PHP + jquery without involving graphic images.









Prelude



Today, the Internet simply abounds with an infinite number of instructions, in which various authors talk about their own way of organizing protection against bots, it is a pity that most of these authors wear a pip in the language. Here, for example, a jQuery plugin called Real Person , it creates something like this on the page:

')





Moreover, it is worth paying attention to the fact that all the letters are created with just one character - an asterisk , without using any images. There are examples on the author’s website that show how easy it is to change the length and set of characters to generate a security code. Also there you will find an example of a server script for checking the correctness of entering characters:

if (rpHash($_POST[ 'realPerson' ]) == $_POST[ 'realPersonHash' ]) {



* This source code was highlighted with Source Code Highlighter .


E-my, and here he is Epic Fail! Unprecedented annoyance, the author suggests that we completely trust the data that came from the user. Such a test is reliable ... does he even believe in it himself ?! Taking into account that the source code of the “rpHash ()” function is also described on the website of the author of this plugin, then with a clear conscience it was possible to write such a check:

if ($_POST[ 'In' ])== $_POST[ 'Out' ]) {



* This source code was highlighted with Source Code Highlighter .


In my opinion, the two lines of code described above are completely identical in terms of the complexity of circumvention by intruders. But still, I believe that this type of Turing test is quite promising and therefore I decided to describe my vision of the organization of such protection.



Solution Method



This CAPTCHA is a set of letters of the Latin alphabet and Arabic numerals. Each character is a 7x7 matrix. Any cell of the matrix can be occupied or free. A busy cell, by default, has a dark background, while a free cell is transparent.





An example of the letter "M".



Any cell can be uniquely determined using two coordinates - x and y. In order to completely recreate a symbol, it is enough to know the coordinates of only one type of cells. It is reasonable to store the coordinates of occupied cells, since their number is several times less than the free ones. An array is used to store this information:

Array (x1, y1, x2, y2 ..., xN, yN)

Based on the above, here is the letter "M":

$abc[ 'm' ] = array(1,1,7,1,1,2,2,2,6,2,7,2,1,3,3,3,5,3,7,3,1,4,4,4,7,4,1,5,7,5,1,6,7,6,1,7,7,7);



* This source code was highlighted with Source Code Highlighter .


When generating a string of characters, you must take into account the offset of the x coordinate, each subsequent character, by n * 7 cells, where n is the number of predecessor characters:





Accordingly, this string can be initialized as an array, resulting from the merging of two arrays m (x, y) and a (x + 7, y).

The generator of the array of row coordinates looks like this:

Php

  1. // all available alphabet
  2. $ alphanum = 'abcdefghijkmnopqrstuvxyz0123456789' ;
  3. // cycle, generate characters,
  4. // the number of loop iterations is equal to the number of characters in the string
  5. for ($ i = 0; $ i <$ the_number_of_letters; ++ $ i) {
  6. // randomly select a character
  7. $ letter = $ alphanum [intval (mt_rand (0, 33))];
  8. // create an array of characters $ array_str
  9. foreach ($ abc [$ letter] as $ key => $ val)
  10. // "correct" coordinates are arranged in the array
  11. array_push ($ array_str, ($ key% 2 == 0)? $ val + ($ i * 7): $ val);
  12. // the string itself is remembered
  13. $ di_captcha_str. = $ letter;
  14. }
* This source code was highlighted with Source Code Highlighter .


Symbols are visualized using a tag with a given left-justification - float: left and clearly defined length and height dimensions. In principle, any block element is suitable for this purpose, but is one of the shortest available tags, so the choice fell on it. The output code itself:

JavaScript (jQuery)

  1. // block length with tags
  2. // the number of tags in each row depends on this value
  3. // calculated by the formula number of cells + size of indent between the cells * 7
  4. // + double_size_cell - indent between characters
  5. // and all this is multiplied by the number of characters n
  6. $ ( '#DICaptchaPic' ) .css ( 'width' , ((((cell_size + 2) * 6) + (3 * cell_size) +1) * n));
  7. // variable to store the list of tags
  8. var html_p_tag = '' ;
  9. // loop traversal of the whole array with cells
  10. for (i = 1; i <= 7 * 7 * n; ++ i) {
  11. // if the cell is a multiple of seven, then it is extreme in the symbol
  12. // and therefore after it, indenting is required
  13. var style = (i% 7 == 0)? 'margin-right:' + 2 * cell_size + 'px;' : '' ;
  14. // if the cell is busy, then its background is black
  15. for (j = 0; j <data [1] .length; j + = 2) style + = (((i% (data [0] * 7) == 0)? (data [0] * 7): i% (data [0] * 7)) == data [1] [j] && Math.ceil (i / (data [0] * 7)) == data [1] [j + 1])? 'background-color: # 000;' : '' ;
  16. // closes the tag
  17. html_p_tag + = '<p' + ((style == '' )? '' : 'style = \' ' + style + ' \ '' ) + '>' ; }
* This source code was highlighted with Source Code Highlighter .


Note The complexity of this algorithm is estimated as O (n * n), due to the nested loop. It can be improved by adding the break construct to the second loop, which will be called when the condition is successful, then in the second loop only part of the array will be viewed, not the entire array, as it is now. It is also possible to bring the inner loop out, and in the first to assign all tags a unique id, by which the second loop easily recognizes them. Although this will lead to a small increase in the code, but the speed of work will increase markedly.

The result is the following lines:





Criticism and refinement



1. It is enough to intercept an array with coordinates when loading it with js `th and compare it with templates to decipher a string. If, before the get () function returns this array, to mix pairs in it, this will complicate the use of templates n times.

Therefore, before returning an array of coordinates, the shuffle2 method is called in the class, which gently shuffles the array, not confusing the x and y pairs.

Php

  1. function shuffle2 ($ array) {
  2. for ($ i = 0; $ i <count ($ array); $ i + = 2)
  3. for ($ j = count ($ array) -2; $ j> $ i; $ j - = 2)
  4. if (mt_rand (0, 1)> 0) {
  5. $ array [$ i] + = $ array [$ j]; $ array [$ j] = $ array [$ i] - $ array [$ j]; $ array [$ i] - = $ array [$ j];
  6. $ array [$ i + 1] + = $ array [$ j + 1]; $ array [$ j + 1] = $ array [$ i + 1] - $ array [$ j + 1]; $ array [$ i + 1] - = $ array [$ j + 1];
  7. }
  8. return $ array;
  9. }
* This source code was highlighted with Source Code Highlighter .




2. If you add random noise to an array of coordinates, it will practically make it impossible to use templates.

The noise will be of two kinds, the noise that clogs the occupied cells, thereby making them free and the noise spreading to the background, which some free cells convert to occupied ones.

For this, the array of row coordinates generator was slightly modified:

Php

  1. // $ this-> noise is initialized when creating a class object
  2. // can take values ​​from 0 (no noise) to 10
  3. $ alphanum = 'abcdefghijkmnopqrstuvxyz0123456789' ;
  4. // main loop
  5. for ($ i = 0; $ i <$ this -> the_number_of_letters; ++ $ i) {
  6. $ letter = $ alphanum [intval (mt_rand (0, 33))];
  7. for ($ j = 0; $ j <count ($ this -> abc [$ letter]); $ j + = 2)
  8. // internal noise
  9. if (mt_rand (1, 100)> $ this -> noise * 5)
  10. array_push ($ this -> array_str, $ this -> abc [$ letter] [$ j] + ($ i * 7), $ this -> abc [$ letter] [$ j + 1]);
  11. // background noise
  12. for ($ j = 0; $ j <7 * 7 * ($ this -> noise / 20); ++ $ j) {
  13. array_push ($ this -> array_str, mt_rand (1, 7) + ($ i * 7), mt_rand (1, 7));
  14. }
  15. $ _SESSION [ 'di_captcha_str' ]. = $ Letter;
  16. }
  17. return $ this -> shuffle2 ($ this -> array_str);
* This source code was highlighted with Source Code Highlighter .


Eventually:





Fully working example



Main class:

Php

  1. // The constructor sets the length of the string of characters, the default is 6.
  2. // class methods:
  3. // shuffle2 () - shuffles the array.
  4. // set () - can change the values ​​of some class fields.
  5. // get () - returns an array of coordinates and puts the string itself into the session.
  6. // check () - accepts user-entered text and compares it with the string recorded in the session.
  7. namespace di;
  8. class captcha {
  9. private $ str, $ array_str = array (), $ abc = array (), $ the_number_of_letters = 6, $ noise = 1;
  10. function __construct ($ the_number_of_letters = 6) {
  11. $ this -> the_number_of_letters = $ the_number_of_letters;
  12. $ this -> abc [ 'a' ] = array (4,1,3,2,5,2,3,3,5,3,2,4,6,4,2,5,3,5,4 , 5,5,5,6,5,1,6,7,6,1,7,7,7);
  13. $ this -> abc [ 'b' ] = array (1,1,2,1,3,1,4,1,5,1,6,1,1,2,7,2,1,3,7 , 3,1,4,2,4,3,4,4,4,5,4,6,4,1,5,7,5,1,6,7,6,1,7,2,7 , 3,7,4,7,5,7,6,7);
  14. $ this -> abc [ 'c' ] = array (2,1,3,1,4,1,5,1,6,1,1,2,7,2,1,3,1,4,1 , 5,1,6,7,6,2,7,3,7,4,7,5,7,6,7);
  15. $ this -> abc [ 'd' ] = array (1,1,2,1,3,1,4,1,5,1,6,1,1,2,7,2,1,3,7 , 3,1,4,7,4,1,5,7,5,1,6,7,6,1,7,7,7,3,7,4,7,5,7,6,7 );
  16. $ this -> abc [ 'e' ] = array (1,1,2,1,3,1,4,1,5,1,6,1,7,1,1,2,1,3,1 4,2,4,3,4,4,4,1,5,1,6,1,7,2,7,3,7,4,7,5,7,7,7,7,7 );
  17. $ this -> abc [ 'f' ] = array (1,1,2,1,3,1,4,1,5,1,6,1,7,1,1,2,1,3,1 4,2,4,3,4,4,4,1,5,1,6,1,7);
  18. $ this -> abc [ 'g' ] = array (2,1,3,1,4,1,5,1,6,1,1,2,7,2,1,3,1,4,1 , 5,5,5,6,5,7,5,1,6,7,6,2,7,3,7,4,7,5,7,6,7);
  19. $ this -> abc [ 'h' ] = array (1,1,7,1,1,2,7,2,1,3,7,3,1,4,2,4,3,4,4 , 4,5,4,6,4,7,4,1,5,7,5,1,6,7,6,1,7,7,7);
  20. $ this -> abc [ 'i' ] = array (1,1,2,1,3,1,4,1,5,1,6,1,7,1,4,2,4,3,4 4,4,5,4,6,1,7,7,7,3,7,4,7,5,7,6,7,7,7);
  21. $ this -> abc [ 'j' ] = array (7,1,7,2,7,3,7,7,7,5,1,6,7,6,2,7,3,7,4 , 7,5,7,6,7);
  22. $ this -> abc [ 'k' ] = array (1,1,7,1,1,2,5,2,6,2,1,3,3,3,3,3,1,4,2 4,1,5,3,5,4,5,1,6,5,6,6,6,1,7,7,7);
  23. $ this -> abc [ 'l' ] = array (1,1,1,2,1,3,1,4,1,5,1,6,1,7,7,7,3,7,4 , 7,5,7,6,7,7,7);
  24. $ this -> abc [ 'm' ] = array (1,1,7,1,1,2,2,2,2,6,2,7,2,1,3,3,3,5,3,7 , 3,1,4,4,4,7,4,1,5,7,5,1,6,7,6,1,7,7,7);
  25. $ this -> abc [ 'n' ] = array (1,1,7,1,1,2,2,2,7,7,1,3,3,3,7,3,1,4,4 , 4,7,4,1,5,5,5,7,5,1,6,6,6,6,6,6,1,7,7,7);
  26. $ this -> abc [ 'o' ] = array (2,1,3,1,4,1,5,1,6,1,1,2,7,2,1,3,7,3,1 , 4,7,4,1,5,7,5,1,6,7,6,2,7,3,7,4,7,5,7,6,7);
  27. $ this -> abc [ 'p' ] = array (1,1,2,1,3,1,4,1,5,1,6,1,1,2,7,2,1,3,7 , 3,1,4,2,4,3,4,4,4,5,4,6,4,1,5,1,6,1,7);
  28. $ this -> abc [ 'q' ] = array (2,1,3,1,4,1,5,1,6,1,1,2,7,2,1,3,7,3,1 4,7,4,1,5,5,5,7,5,1,6,6,6,2,7,3,7,4,7,5,7,7,7);
  29. $ this -> abc [ 'r' ] = array (1,1,2,1,3,1,4,1,5,1,6,1,1,2,7,2,1,3,7 , 3,1,4,2,4,3,4,4,4,5,4,6,4,1,5,5,5,1,6,6,6,1,7,7,7 );
  30. $ this -> abc [ 's' ] = array (2,1,3,1,4,1,5,1,6,1,1,2,7,2,1,3,2,4,3 4,4,4,5,4,6,4,7,5,1,6,7,6,2,7,3,7,4,7,5,7,6,7);
  31. $ this -> abc [ 't' ] = array (1,1,2,1,3,1,4,1,5,1,6,1,7,1,4,2,4,3,4 4,4,5,4,6,4,7);
  32. $ this -> abc [ 'u' ] = array (1,1,7,1,1,2,7,2,1,3,7,3,1,4,7,4,1,5,7 , 5,1,6,7,6,2,7,3,7,4,7,5,7,6,7);
  33. $ this -> abc [ 'v' ] = array (1,1,7,1,1,2,7,2,2,3,3,3,2,4,6,4,3,5,5 5,3,6,5,6,4,7);
  34. $ this -> abc [ 'w' ] = array (1,1,7,1,1,2,7,2,1,3,7,3,1,4,4,4,7,4,1 5,3,5,5,5,7,5,1,6,2,6,6,6,6,6,6,1,7,7,7);
  35. $ this -> abc [ 'x' ] = array (1,1,7,1,2,2,6,2,3,3,5,3,4,4,3,5,5,5,2 , 6,6,6,1,7,7,7);
  36. $ this -> abc [ 'y' ] = array (1,1,7,1,2,2,6,2,3,3,5,3,4,4,4,5,4,6,4 , 7);
  37. $ this -> abc [ 'z' ] = array (1,1,2,1,3,1,4,1,5,1,6,1,7,1,6,2,5,3,4 4,3,5,2,6,1,7,7,7,3,7,4,7,5,7,6,7,7,7);
  38. $ this -> abc [ '0' ] = array (3,1,4,1,5,1,2,2,2,2,2,1,3,5,3,7,3,1,4,4 , 4,7,4,1,5,3,5,7,5,2,6,6,6,3,7,7,4,7,5,7);
  39. $ this -> abc [ '1' ] = array (4,1,3,2,4,2,2,3,3,3,4,4,4,5,4,6,1,7,7 , 7.3,7,4,7,5,7,6,7,7,7);
  40. $ this -> abc [ '2' ] = array (2,1,3,1,4,1,5,1,6,1,1,2,7,7,7,3,6,4,4 , 5,5,5,2,6,3,6,1,7,7,7,3,7,4,7,5,7,6,7,7,7);
  41. $ this -> abc [ '3' ] = array (2,1,3,1,4,1,5,1,6,1,1,2,7,7,7,3,5,4,6 , 4,7,5,1,6,7,6,2,7,3,7,4,7,5,7,6,7);
  42. $ this -> abc [ '4' ] = array (5,1,4,2,5,2,3,3,5,3,2,4,5,4,1,5,2,5,3 5,4,5,5,5,6,5,7,5,5,6,5,7);
  43. $ this -> abc [ '5' ] = array (1,1,2,1,3,1,4,1,5,1,6,1,7,1,1,2,1,3,2 , 3,3,3,4,3,5,3,6,3,7,4,7,5,1,6,7,6,2,7,3,7,4,7,5,7 , 6.7);
  44. $ this -> abc [ '6' ] = array (3,1,4,1,5,1,6,1,2,2,1,3,1,4,2,4,3,4,4 , 4,5,4,6,4,1,5,7,5,1,6,7,6,2,7,3,7,4,7,5,7,6,7);
  45. $ this -> abc [ '7' ] = array (1,1,2,1,3,1,4,1,5,1,6,1,7,1,6,2,5,3,4 , 4,3,5,2,6,1,7);
  46. $ this -> abc [ '8' ] = array (2,1,3,1,4,1,5,1,6,1,1,2,7,2,1,3,7,3,2 , 4,3,4,4,4,5,4,6,4,1,5,5,5,1,6,7,6,2,7,3,7,4,7,5,7 , 6.7);
  47. $ this -> abc [ '9' ] = array (2,1,3,1,4,1,5,1,6,1,1,2,7,2,1,3,7,3,2 , 4,3,4,4,4,5,4,6,4,7,4,7,5,6,6,2,7,3,7,4,7,5,7);
  48. }
  49. private function shuffle2 ($ array) {
  50. for ($ i = 0; $ i <count ($ array); $ i + = 2)
  51. for ($ j = count ($ array) -2; $ j> $ i; $ j - = 2)
  52. if (mt_rand (0, 1)> 0) {
  53. $ array [$ i] + = $ array [$ j]; $ array [$ j] = $ array [$ i] - $ array [$ j]; $ array [$ i] - = $ array [$ j];
  54. $ array [$ i + 1] + = $ array [$ j + 1]; $ array [$ j + 1] = $ array [$ i + 1] - $ array [$ j + 1]; $ array [$ i + 1] - = $ array [$ j + 1];
  55. }
  56. return $ array;
  57. }
  58. function set ($ name, $ val) {
  59. switch ($ name) {
  60. case 'the_number_of_letters' :
  61. $ this -> the_number_of_letters = ( int ) $ val;
  62. break ;
  63. case 'noise' :
  64. $ this -> noise = ( int ) $ val;
  65. break ;
  66. default :
  67. return false ;
  68. }
  69. return true ;
  70. }
  71. function get () {
  72. $ alphanum = 'abcdefghijkmnopqrstuvxyz0123456789' ;
  73. unset ($ _ SESSION [ 'di_captcha_str' ]);
  74. for ($ i = 0; $ i <$ this -> the_number_of_letters; ++ $ i) {
  75. $ letter = $ alphanum [intval (mt_rand (0, 33))];
  76. // foreach ($ this-> abc [$ letter] as $ key => $ val)
  77. // array_push ($ this-> array_str, ($ key% 2 == 0)? $ val + ($ i * 7): $ val);
  78. for ($ j = 0; $ j <count ($ this -> abc [$ letter]); $ j + = 2)
  79. if (mt_rand (1, 100)> $ this -> noise * 5)
  80. array_push ($ this -> array_str, $ this -> abc [$ letter] [$ j] + ($ i * 7), $ this -> abc [$ letter] [$ j + 1]);
  81. for ($ j = 0; $ j <7 * 7 * ($ this -> noise / 20); ++ $ j) {
  82. array_push ($ this -> array_str, mt_rand (1, 7) + ($ i * 7), mt_rand (1, 7));
  83. }
  84. $ _SESSION [ 'di_captcha_str' ]. = $ Letter;
  85. }
  86. return $ this -> shuffle2 ($ this -> array_str);
  87. }
  88. function check ($ in_string) {
  89. echo $ in_string. '|' . $ _ SESSION [ 'di_captcha_str' ];
  90. return (strtolower ($ in_string) == $ _SESSION [ 'di_captcha_str' ])? true : false ;
  91. }
  92. }
* This source code was highlighted with Source Code Highlighter .


After creating the phar and compressing it, you get the di_captcha.class.phar.gz file with a weight of 3.53kb.

Example class usage:

PHP + html

// index.php

//

session_start();

// THE_NUMBER_OF_LETTERS – ,

define( 'THE_NUMBER_OF_LETTERS' , 6);

// js , .

if (isset($_POST[ 'action' ]{14}) && $_POST[ 'action' ] == 'captcha_refresh' ) {

require 'phar://di_captcha.class.phar.gz/di_captcha.class.php' ;

$captcha = new di\captcha();

$captcha-> set ( 'noise' , 0);

echo json_encode(array(THE_NUMBER_OF_LETTERS, $captcha-> get ()));

} else {



? > <br> <! DOCTYPE html > <br> < html > <br> < head > <br> < meta charset ='utf-8' > <br> < title > Test </ title > <br> < link rel ='stylesheet' media ='all' href ='style.css' > <br> < script type ='text/javascript' src ='jquery-1.6.1.min.js' charset ='utf-8' ></ script > <br> src= 'script.js' charset= 'utf-8' > </ script > <br> </ head > <br> < body > <br> < p id ='Title' > -, ! </ p > <br> < p id ='Msg' > <br> <? php <br> if ( isset ($ _POST [ 'action' ]{ 11 }) &# && ; $ _POST [ 'action' ] == 'captcha_send' ) {;<br> require 'phar://di_captcha.class.phar.gz/di_captcha.class.php' ;<br> $ captcha = new di \ captcha ();<br> echo ($ captcha- > check($_POST['text_captcha']))?'- !':' , ...';<br> }<br> ? > <br> </ p > <br> < form action ='index.php' method ='post' > <br> < div id ='DICaptchaPic' ></ div > <br> < p style ='padding: 0 10px;' > <br> < input type ='text' name ='text_captcha' id ='text_captcha' value ='<?php echo $_POST[' text_captcha ']; ? > ' placeholder='6 ' >< br >< label for ='text_captcha' > *a- </ label > < ahref ='#' onclick ='di_captcha_refresh(); return false;' > </ a > <br> </ p > <br> < p style ='padding: 10px 0;' > <br> < input type ='hidden' name ='action' value ='captcha_send' /> <br> < input type ='submit' name ='submit' value ='' /> <br> </ p > <br> </ form > <br> </ body > <br> </ html > <br> <? php<br> } <br> * This source code was highlighted with Source Code Highlighter .



When asking from js to generate a string, the number of characters and the array of coordinates are returned.

And, actually, js script:

Js

  1. / * script.js * /
  2. / * cell_size - cell size in pixels * /
  3. var cell_size = 3;
  4. function di_captcha_refresh () {
  5. $ .post ( './index.php' , {action: 'captcha_refresh' },
  6. function (data) {
  7. var data = eval (data);
  8. $ ( '#DICaptchaPic' ) .css ( 'width' , ((((cell_size + 2) * 6) + (3 * cell_size) +1) * data [0]));
  9. var html_p_tag = '' ;
  10. for (i = 1; i <= 7 * 7 * data [0]; ++ i) {
  11. var style = (i% 7 == 0)? 'margin-right:' + 2 * cell_size + 'px;' : '' ;
  12. for (j = 0; j <data [1] .length; j + = 2) style + = (((i% (data [0] * 7) == 0)? (data [0] * 7): i% (data [0] * 7)) == data [1] [j] && Math.ceil (i / (data [0] * 7)) == data [1] [j + 1])? 'background-color: # 000;' : '' ;
  13. html_p_tag + = '<p' + ((style == '' )? '' : 'style = \' ' + style + ' \ '' ) + '> '
} $ ( '#DICaptchaPic' ) .html (html_p_tag); })} $ ( document ) .ready ( function () {$ ( '#DICaptchaPic' ) .css ( 'overflow' , 'hidden' ); $ ( '#DICaptchaPic' ) .css ( 'height' , (cell_size + 2) * 7); di_captcha_refresh (); $ ( '#DICaptchaPic' ) .click ( function () {di_captcha_refresh ();}); $ ( '#text_captcha' ) .focus ()}) * This source code was shared with Source Code Highlighter .






PS



1. If you add character patterns to the class of string generation, several patterns per character, this will further complicate the interpretation by means of patterns.

2. The _http: //decaptcher.com, _http: //captchabot.com and _http: //antigate.com/ workers will thank you for such a captcha.

3. This article was published by my friend, in the web development section a few days ago, I gained 16 minuses, 14 pluses and 11 comments. Then she was buried by moderators for the reason “she cannot read the site rules (publishes other people's posts and invites invites)”, the article was written specifically for the site, and no one invites invites, but specified that he was not the author of the article. Then she hit the sandbox and deserved an invite.

4. A brief sense of 11 comments:

- to bypass the captcha you need to intercept the post request and decrypt the text by templates

- with noise at decoding there will be a big marriage

- with noise, the letters “O” and “D”, “C” and “G” are sometimes difficult to choose and get confused, it is better to exclude them from the alphabet

- you can decrypt any captcha, the main thing is to find a compromise between readability and circumvention complexity



Download source



Demo: no noise , noisy (1)



UPD: nagato developed a script that successfully bypasses captcha in 70% of cases.

Source: https://habr.com/ru/post/121794/



All Articles