Skip to content

Instantly share code, notes, and snippets.

@Logioniz
Last active August 30, 2018 19:31
Show Gist options
  • Save Logioniz/1111c5c3167de70c07933a67e69e20cc to your computer and use it in GitHub Desktop.
Save Logioniz/1111c5c3167de70c07933a67e69e20cc to your computer and use it in GitHub Desktop.
rucaptcha аналог (решение гугл рекапчи с помощью работников/людей)
3 компоненты:
1. простенький api service, который чем-то похож на апи rucaptcha для приёма recaptcha. Нужно передать рекапча ключ и урл.
2. простенький socks5 прокси, который умеент подменять запросы, когда это нужно (чтобы домен (url) совпадал с доменом разгадываемой капчей)
3. браузер работника должен быть настроен на проксирование через наш socks5 прокси
Первым делом нужно запустить прокси и апи серверы.
Потом нужно дождаться, когда подключится работник (тот, кто капчу разгадывает), при этом работник должен настроить в браузере прокси socks5 с разыменование домена.
Потом сделать запрос на добавление капчи:
curl -XPOST --data-urlencode "googlekey=<публичный ключ рекапчи>" --data-urlencode "pageurl=<страница на которой разгадываем>" "http://127.0.0.1:3000/in.php"
Далее работник должен разгадать капчу, после чего ответ уйдёт апи сервису.
И клиент может забирать результат запросом:
curl "http://127.0.0.1:3000/res.php?id=<id, выданные на этапе добавления капчи>"
Так же можно проверить результат, если у вас есть секретный ключ от рекапчи:
curl --data-urlencode "secret=<секретный ключ>" --data-urlencode "response=<ответ для рекапчи>" --data-urlencode "remoteip=<ip адрес, не обязательный параметр>" "https://www.google.com/recaptcha/api/siteverify"
На момент написания, гугл не проверял ip адрес разгадывающего.
#!/usr/bin/perl
use Mojo::Base -strict;
use Mojolicious::Lite;
use Mojo::URL;
use DDP;
my $server = '127.0.0.1:3000';
my ($tasks, $workers) = ({}, {});
# worker
get '/' => {host => $server} => 'index';
websocket '/ws' => sub {
my $c = shift;
$c->inactivity_timeout(30000);
$workers->{$c} = $c;
$c->on(message => sub { });
$c->on(finish => sub {
my ($c, $code, $reason) = @_;
delete $workers->{$c} if exists $workers->{$c};
});
};
get '/result' => sub {
my $c = shift;
my $url = $c->param('url');
my $resp = $c->param('g-recaptcha-response');
say $resp;
for my $id (keys %$tasks) {
my ($u, $key, $handle) = @{$tasks->{$id}}{qw/url key handle/};
$tasks->{$id}{resp} = $resp if $url eq $u;
}
return $c->redirect_to("//$server/");
};
# proxy
get '/domain/check' => sub {
my $c = shift;
my $domain = $c->param('domain');
my ($res_key, $res_url, $is_found) = 0;
for my $id (keys %$tasks) {
my ($url, $key, $handle) = @{$tasks->{$id}}{qw/url key handle/};
if ($url =~ m/$domain/ and !$handle) {
$is_found = 1;
$tasks->{$id}{handle} = 1;
($res_key, $res_url) = ($key, $url);
}
}
return $c->render(json => {status => 'fail'}) unless $is_found;
$c->render(json => {
status => 'ok',
key => $res_key,
url => $res_url
});
};
# client
any 'in.php' => sub {
my $c = shift;
my $key = $c->param('googlekey');
my $url = $c->param('pageurl');
my $id = 100000000 + int rand 100000000;
unless (keys %$workers) {
return $c->render(code => 500, text => 'workers not found');
}
$url =~ s/https:\/\//http:\/\//;
$tasks->{$id} = {url => $url, key => $key, handle => 0};
my $worker = delete $workers->{(keys %$workers)[0]};
$worker->send($url);
$c->render(text => "OK|$id");
};
get 'res.php' => sub {
my $c = shift;
my $id = $c->param('id');
return $c->reply->not_found unless $tasks->{$id};
if ($tasks->{$id}{resp}) {
my $task = delete $tasks->{$id};
return $c->render(text => "OK|" . $task->{resp});
}
return $c->render(text => 'CAPCHA_NOT_READY');
};
app->start;
__DATA__
@@ index.html.ep
Waiting google recaptcha...
<script>
var ws = new WebSocket("ws://<%= $host %>/ws");
ws.onmessage = function(event) {
var url = event.data;
alert("Goto " + url);
window.location.href = url;
};
ws.onerror = function(error) {
alert("Error " + error.message);
};
</script>
#!/usr/bin/perl
use Mojo::Base -strict;
use Mojo::IOLoop;
use Devel::Peek;
use Data::Dumper;
use Mojo::UserAgent;
use Mojo::URL;
my $api_url = "http://127.0.0.1:3000";
my $domain_check_url = "$api_url/domain/check";
my $result_url = "$api_url/result";
my $DEBUG = 0;
my $users = {
# user1 => 'pass1'
};
sub _debug {
my ($type, @params) = @_;
say '-----------';
say $type;
my (@columns, @values);
while (@params) {
my ($c, $v) = splice @params, 0, 2;
push @columns, sprintf('%15s|', $c);
push @values, sprintf('%15s|', $v);
}
say join '', @columns if @columns;
say join '', @values if @values;
say '';
}
sub init_handshake {
my ($stream, $bytes) = @_;
my ($ver, $nmethods, @methods) = unpack 'C*', $bytes;
_debug('Receive', ver => $ver, nmethods => $nmethods, methods => join('', @methods)) if $DEBUG;
if ($ver != 5) {
_debug('Close connection') if $DEBUG;
return $stream->close;
}
my %methods = map { $_ => 1 } @methods;
if (%$users and ! $methods{2}) {
$stream->write(pack'C*', 5, 0xff);
if ($DEBUG) {
_debug('Send', ver => 5, method => 0xff);
_debug('Close connection');
}
return $stream->close;
}
if (! %$users and ! $methods{0}) {
$stream->write(pack'C*', 5, 0xff);
if ($DEBUG) {
_debug('Send', ver => 5, method => 0xff);
_debug('Close connection');
}
return $stream->close;
}
$stream->unsubscribe(read => $stream->subscribers('read')->[-1]);
%$users
? $stream->on(read => \&auth_handshake)
: $stream->on(read => \&command_handshake);
$stream->write(pack'C*', 5, %$users ? 2 : 0);
_debug('Send', ver => 5, method => %$users ? 2 : 0) if $DEBUG;
}
sub auth_handshake {
my ($stream, $bytes) = @_;
my ($ver, $ulen) = unpack 'C*', substr($bytes, 0, 2);
$bytes = substr($bytes, 2);
my $uname = substr($bytes, 0, $ulen);
$bytes = substr($bytes, $ulen);
my $plen = unpack 'C', substr($bytes, 0, 1);
$bytes = substr($bytes, 1);
my $passwd = substr($bytes, 0, $plen);
my $status = 1;
$status = 0 if $users->{$uname} && $users->{$uname} eq $passwd;
_debug('Receive', ver => $ver, ulen => $ulen, uname => $uname, plen => $plen, passwd => $passwd) if $DEBUG;
if ($ver != 1 || $status != 0) {
$stream->write(pack'C*', 1, 1);
if ($DEBUG) {
_debug('Send', ver => 1, status => 1);
_debug('Close connection');
}
return $stream->close;
}
$stream->unsubscribe(read => $stream->subscribers('read')->[-1]);
$stream->on(read => \&command_handshake);
$stream->write(pack'C*', 1, $status);
_debug('Send', ver => 1, status => $status) if $DEBUG;
}
sub command_handshake {
my ($stream, $bytes) = @_;
my ($ver, $cmd, $rsv, $atyp) = unpack 'C*', substr($bytes, 0, 4);
$bytes = substr($bytes, 4);
my $addr;
if ($atyp == 1) {
$addr = join '.', unpack('C4', $bytes);
$bytes = substr($bytes, 4);
} elsif ($atyp == 3) {
my $addr_len = unpack 'C', substr($bytes, 0, 1);
$bytes = substr($bytes, 1);
$addr = substr($bytes, 0, $addr_len);
$bytes = substr($bytes, $addr_len);
} elsif ($atyp == 4) {
$addr = join ':', unpack('(H4)8', $bytes);
$bytes = substr($bytes, 16);
}
my $port = unpack 'n', $bytes;
_debug('Receive', ver => $ver, cmd => $cmd, rsv => $rsv, atyp => $atyp, addr => $addr, port => $port) if $DEBUG;
my $rep = 0;
$rep = 1 if $ver != 5;
$rep = 7 if $cmd != 1;
$rep = 8 if $atyp != 1 && $atyp != 3 && $atyp != 4;
if ($rep != 0) {
$stream->write(pack'C*', 5, $rep, 0, 1, 0, 0, 0, 0, 0, 0);
if ($DEBUG) {
_debug('Send', ver => 5, rep => $rep, rsv => 0, atyp => 1, addr => '0.0.0.0', port => 0);
_debug('Close connection');
}
return $stream->close;
}
my $ua = Mojo::UserAgent->new;
my $res = $ua->get(Mojo::URL->new($domain_check_url)->query(domain => $addr))->result->json;
if ($res->{status} eq 'ok') {
use DDP;
p $res;
$stream->unsubscribe(read => $stream->subscribers('read')->[-1]);
$stream->on(read => sub { spoofing(@_, $res) });
$stream->write(pack('C*', 5, 0, 0, 1) . pack('C*', 127, 0, 0, 1) . pack('n', 31337));
if ($DEBUG) {
_debug('Send', ver => 5, rep => 0, rsv => 0, atyp => 1, addr => '127.0.0.1', port => '31337');
}
return;
}
Mojo::IOLoop->client({address => $addr, port => $port, timeout => 5} => sub {
my ($loop, $err, $foriegn_stream) = @_;
if ($err) {
my $rep = 0xff;
$rep = 3 if $err =~ m/Transport endpoint is not connected/;
$rep = 4 if $err =~ m/Can't resolve/;
$rep = 4 if $err =~ m/Can't connect/;
$rep = 5 if $err =~ m/Connection refused/;
$rep = 6 if $err =~ m/timeout/;
$stream->write(pack'C*', 5, $rep, 0, 1, 0, 0, 0, 0, 0, 0);
if ($DEBUG) {
say $err;
_debug('Send', ver => 5, rep => $rep, rsv => 0, atyp => 1, addr => '0.0.0.0', port => 0);
_debug('Close connection');
}
return $stream->close;
}
if (!$stream->handle or !$stream->handle->connected) {
$stream->close;
return $foriegn_stream->close;
}
$stream->timeout(0);
$foriegn_stream->timeout(0);
$stream->unsubscribe(read => $stream->subscribers('read')->[-1]);
my $state = {};
io_streams($state, 1, $stream, $foriegn_stream);
io_streams($state, 0, $foriegn_stream, $stream);
my $localaddr = $foriegn_stream->handle->sockaddr;
my $localport = $foriegn_stream->handle->sockport;
my $atyp = length $localaddr > 4 ? 4 : 1;
$stream->write(pack('C*', 5, 0, 0, $atyp) . $localaddr . pack('n', $localport));
if ($DEBUG) {
my ($template, $separator) = $atyp == 1 ? ('C*', '.') : ('(H4)*', ':');
my $localaddr_str = join $separator, unpack $template, $localaddr;
_debug('Send', ver => 5, rep => 0, rsv => 0, atyp => $atyp, addr => $localaddr_str, port => $localport);
}
});
}
sub io_streams {
my ($state, $is_client, $stream1, $stream2) = @_;
$stream1->on('close' => sub {
$stream2->close;
undef $stream2;
});
$stream1->on('error' => sub {
my ($stream, $err) = @_;
$stream2->close;
undef $stream2;
});
$stream1->on('read' => sub {
my ($stream, $bytes) = @_;
$stream2->write($bytes);
});
}
sub spoofing {
my ($stream, $bytes, $obj) = @_;
my ($key, $url) = @$obj{qw/key url/};
my $message = "
<script src='https://www.google.com/recaptcha/api.js'></script>
<form action='$result_url' method='GET'>
<div class='g-recaptcha' data-sitekey='$key'></div>
<input type='text' name='url' value='$url'><br>
<input type='submit'>
</form>
";
my $host = Mojo::URL->new($url)->host;
my $content_length = length $message;
$stream->write("HTTP/1.1 200 OK\r
Host: $host\r
Content-Type: text/html\r
Connection: close\r
Content-Length: $content_length\r
\r
$message
" => sub {
my $stream = shift;
$stream->close;
});
}
Mojo::IOLoop->server({port => 3001} => sub {
my ($loop, $stream) = @_;
$stream->timeout(10);
$stream->on(read => \&init_handshake);
});
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment