nginx+captcha

jch nginx-forum на nginx.us
Пн Июн 14 19:34:29 MSD 2010


Набросал прототип с использованием встроенного perl
(если бы этот патчик http://catap.ru/blog/2009/05/13/nginx-crc32_name-and-md5_name/ позволял  собирать 0.8.40, то перл был бы не нужен вовсе)

Работает оно так - новый юзер редиректится на страничку с капчой test.html, капча в ней берется через ngx_random_index_module из папки с набором картинок и одновременно юзеру отправляется кука, вычисляемая как crc32($captcha_content.$remote_addr)
Картинки капчи заранее разгаданы и называются сответственно своему содержимому, например ki1q2.png ;)

Человек вводит капчу и по кнопке "отправить" его вариант GET-ом летит на сервер в виде запроса /rcaptcha/?input=blabla
Там производится сравнение куки ct и присланного инпута и в случае успеха выдаётся кука ha, которая считается как crc32('secret word'.$remote_addr), или, если капча введена неверно, делается редирект обратно на test.html.

С $cookie_ha нашему юзеру теперь везде есть ход.

Таким образом никаких данных на сервере не хранится, и для проверки правильности ввода капчи нужно производить лишь сравнительно быстрые вычисления crc32.

Можно было сделать всё это в виде одного перломодуля, но не случилось. Текущий вариант несколько неизящен из-за дублирования кода в определениях переменных,  но зато весьма быстр ( 550Мбит/c трафика через ab с 50000r/s, LA на сервере поднялся лишь до 4-х ).

[code]
http {
...

    perl_set $crc32_ct '
    sub {
        use String::CRC32;
        my $r = shift;
        my $ct=$r->uri;
        $ct=~ s/.*\/captchas\/(.+)\.png/$1/g;
        return crc32($ct.$r->remote_addr);
    }';

    perl_set $check_input '
    sub {
        use String::CRC32;
        my $r = shift;
        my $input=$r->args;
        $input=~ s/.*input=(.+)/$1/g;
        my $cookie_ct=$r->header_in("Cookie");
        $cookie_ct=~ s/.+ct=([^\ ]+).*/$1/g;
        if ( $cookie_ct == crc32($input.$r->remote_addr) ) { return 1; } else {return 0;}
        #return crc32($ct.$r->remote_addr);
    }';

    perl_set $crc32_ha '
    sub {
        use String::CRC32;
        my $r = shift;
        return crc32($r->remote_addr."secret word");
    }';


    perl_set $check_ha '
    sub {
        use String::CRC32;
        my $r = shift;
        my $cookie_ha=$r->header_in("Cookie");
        $cookie_ha=~ s/.+ha=([^\ ]+).*/$1/g;
        #if ( !$cookie_ha ) return 0;
        if ( crc32($r->remote_addr."secret word") == $cookie_ha ) { return 1; } else {return 0;}
    }';
    

    server {
        listen      80;
        server_name     test;
        root /opt/www/test;

        location /test.html {
            if ( $fu ) {
               add_header Set-Cookie "fu=$fu; path=/";
            }
        }
        location / {
            if ( $check_ha = "0" ) { # Если нет волшебной куки, выставляемой после верного ввода капчи, или она неверна - редирект.
                set $fu "$request_uri";
                rewrite ^ /test.html;
            }
        }

        location /captchas/ {  # В папке captchas лежат капчи в виде картинок, имя файла равно содержимому ;)
            random_index  on;
            add_header Set-Cookie "ct=$crc32_ct; path=/";
        }

        location /rcaptcha/ {  # Сюда прилетают попытки отгадать капчу
            if ( $check_input ) { #  Если всё верно - выставляем волшебную куку ha, по которой везде пускают
                add_header Set-Cookie "ha=$crc32_ha; path=/"; 
                rewrite ^ $cookie_fu redirect;  # И делаем редирект на изначально запрошенную юзером страницу.
            }  
            rewrite ^ /test.html redirect; # Неверно - обратно на капчу.
        }

        error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location ~ /(50x|404).html {
            root   html;
        }
    }
}
[/code]

Содержимое файла test.html:

[code]
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>Капча</title>
<style type="text/css">
* { font-size: 100.01%; }
body { background: #fff; font-size: 0.8em; }
div { margin-top: 1em; font: 175% Arial, sans-serif; }
</style>
</head>

<body>
<table border="0" width="100%" style="height: 100%">
<tr><td align="center"><img src="/captchas/"  border="0" alt="Captcha"/>

 <form name="test" method="get" action="/rcaptcha/">
  <p><b>Введите символы, изображенные на картинке:</b><br>
   <input name="input" type="text" size="6">
  </p>
  <p><input type="submit" value="Отправить">
   <input type="reset" value="Очистить"></p>
 </form>

</td></tr></table></body></html>
[/code]

Мотивированная критика и анализ возможностей обойти капчу всячески приветствуются ;)

Posted at Nginx Forum: http://forum.nginx.org/read.php?21,95997,98028#msg-98028




Подробная информация о списке рассылки nginx-ru