В сети постоянно появляются все новые и новые статьи о том, как написать сканер или веб флудер или другой
скрипт на Perl, главным принципом работы которого
является работа с http протоколом. Но большинство читателей, ознакомившись с материалом, так и не поймут принципа реализации этого скрипта, т.к.
в подобных статьях уделяется очень маленькое внимание ядру скрипта - http запросам. Об этом я хотел
рассказать вам в данной статье. Вся статья делиться на две части, которые посвящены двум разным решениям
на Perl: IO::Socket и LWP. Вкратце опишу достоинства каждого из решений и
области их применения. 

LWP (LWP - The World-Wide Web library for Perl) это библиотека модулей, которая позволяет вам в полной мере
использовать возможности http протокола.
Структура этой библиотеки в стандартной поставке Perl такова:

  • Authen - модуль опознавания.
  • Ntlm - позволяет использовать NTLM протокол для
    аутентификации.
  • ConnCache - модуль для управления кэшем сеанса связи.
  • Debug - модуль для отслеживания выполнения запросов.
  • DebugFile - этот модуль очень похож на предыдущий, но нужен он для того чтобы понять что делает LWP
    в момент ошибки при использовании этой библиотеки.
  • MediaTypes - модуль для работы с MIME types. Проще говоря, этот модуль помогает
    распознавать медиа
    файлы переданные или отправленные вами по сети.
  • MemberMixin - этот модуль наверное используется намного реже других в этой библиотеке и имеет всего
    один метод, который позволяет изменять значения переменных в хэше %self, который используется в конструкторах объектов класса.
  • Protocol - базовый класс для работы со всеми протоколами, которые поддерживает
    LWP. 
  • RobotUA - это класс нужен для конструирования самых настоящих веб роботов, киборгов и прочей техники! 
  • Simple - название говорит само за себя: простейший
    интерфейс для работы с LWP.
  • UserAgent - очень важный класс в данной библиотеке. Он
    объединяет HTTP::Request, HTTP::Response и LWP::UserAgent и позволяет
    создавать объекты этих классов.

Ну вот, теперь вы имеете некоторое представление о структуре LWP. А теперь
представьте себе модуль, который сможет сделать все тоже самое, но справится со всем этим один. Да-да,
дамы и господа, встречайте, IO::Socket.

Этот модуль служит для использования сокетов в Perl. Выражаясь
абстрактно, сокеты - это конечные точки потока данных который протекает между двумя компьютерами. Таким
образом, c помощью данного модуля, вы можете отправлять и принимать пакеты используя любые протоколы (даже можете
придумать свой). Но сегодня мы не будем изобретать новые протоколы, а поговорим лишь о http. Итак, вот перед
нами два решения: LWP и IO::Socket. В чем их отличия и какое из них лучше использовать в конкретных ситуациях?
Начнем с LWP. Нужно сказать, что в основу LWP лег модуль IO::Socket и я думаю, что этим все сказано. LWP - это
более высокий, абстракционный уровень программирования сокетов. Когда использовать LWP?
Честно говоря, все зависит от ситуации. Есть задачи, которые решаются с помощью LWP за день, а использование сокетов приведет к написанию
нового LWP, поэтому оно не рационально. С другой, стороны использование LWP на серверах с урезанным перлом в
приложениях, где скорость критична, не представляется мне возможным. Давайте подробнее рассмотрим оба решения
для того, чтобы понять как они работают.

LWP.

Итак, давайте разберем простой GET запрос с использованием модулей
LWP. Самый простой способ это воспользоваться модулем
LWP::Simple.

use LWP::Simple;
$doc = get('http://www.google.com');
print $doc;

Как видите, не затратив больших усилий мы получили нужную страницу. А теперь давайте перейдем
к более интересной форме GET запроса с помощью модуля
LWP::UserAgent.

use LWP::UserAgent;
my $ua = LWP::UserAgent->new(
max_size => 1024,
agent => 'xakep agent v 1.0',
timeout => 30,
);
$request = HTTP::Request->new('GET', 'http://127.0.0.1/');
$response = $ua->request($request);
print $response->content;

Как видите, объем кода немного увеличился, но теперь, используя объект LWP::UserAgent, мы получаем
доступ к настройкам запроса. В данном примере я установил timeout сервера равным 30, дал имя нашему
клиенту, а также установил максимальную величину (в байтах) ответа сервера. На самом деле я использовал
далеко не все настройки данного объекта. Часть остальных опций я опишу ниже. Стоит также отметить, что вместо
функции get(), как в прошлом примере, в данном случае
использовался конструктор объекта HTTP::Request, у которого также
есть свои интересные методы, о которых мы поговорим чуть позже. Итак, теперь перейдем от GET запросов к
POST. Вам, флудеры, кардеры и прочие нарушители сетевого порядка, гораздо чаще придется работать именно с этим
видом запросов, т.к POST запросы чаще GET используют для передачи данных скриптам, с которыми вы и
будете работать. Итак, от слов к примеру:

use LWP::UserAgent;
my $ua = LWP::UserAgent->new(
max_size => 1024,
agent => 'xakep agent v 1.0',
timeout => 30,
);
my $req = HTTP::Request->new(POST => 'http://127.0.0.1');
$req->content('vote_id=31337&choice=Slipknot');
$req->referer('http://127.0.0.1');
my $res = $ua->request($req);
print $res->as_string;

Вот перед нами типичный POST запрос. Как и в предыдущем примере, мы используем конструктор объекта HTTP::Request
для создания запроса. Метод content() отвечает за передачу данных скрипту. Собственно в данном случае мы
передаем скрипту идентификатор голосования
и наш голос, который мы присудили группе Slipknot. Немного модифицировав
этот короткий скрипт, вы получите вполне работоспособный накрутчик
голо сований. Метод referer() необходим во многих случаях, т.к "хитрые" программисты не хотят, чтобы такие
безбашенные парни как Slipknot выигрывали голосования
и для этого ставят проверку поля REFERER, в котором
(как вы знаете) передается адрес страницы с которой был
сделан запрос. Ну и конечно же "хитрые"  программисты будут очень сильно возражать, если мы будем голосовать много
тысяч раз с одного ip адреса. Итак, опишу
вкратце суть коннекта через прокси сервер.

$ua->proxy($proto => $proto."://$server");
ну или проще говоря
$ua->proxy(http => 'http://proxy.com');

Как видите в LWP предусмотрен специальный метод proxy(), в которым вы указываете схему протокола и сам адрес
прокси сервера. Ну и далее у нас еще 2 темы, которые я хотел бы с вами разобрать. Это обработка редиректа с помощью
LPW и запросы по защищенному https протоколу. Начнем со второго. Для начала хочу сказать, что под
Windows https запроса с помощью LWP вы наверняка не сделаете. Если хотите проверить, то выполните следующий код у себя на машине:

use LWP::UserAgent;
$ua=LWP::UserAgent->new();
if($ua->is_protocol_supported('https'))
{ print "https scheme exists" }
else { print "https scheme does not exist" }

Проверили? Работает на *nix? Ну вот и славно. А теперь спешу вас обрадовать: запрос по https протоколу
ничем почти не отличается от обычного...

$request = HTTP::Request->new('GET', 'https://127.0.0.1/');

Ну и последнее. Очень часто скрипт, которому передаются данные,
перенаправляет вас на страницу результатов. И поэтому
обычны POST или GET запрос здесь вам не поможет, т.к. вместо страницы результатов сервер передаст клиенту ответ типа 30x.
Для того, чтобы нам перейти на страницы редиректа, добавим несколько строк к нашему скрипту перед тем как делать запрос.

push @{ $ua->requests_redirectable }, 'POST';
$ua->redirect_ok($request);

Сразу оговорюсь, что первая строчка нужна только в том случае, если вы используете POST запрос. Она добавляет в специальный
массив, в котором хранятся виды перенаправляемых запросов, значение POST. И вторая строка собственно обеспечивает само
перенаправление запроса.

IO::Socket

Начнем, как и в прошлом разделе, с простого GET запроса.

require IO::Socket;
my $sock = IO::Socket::INET->new(PeerAddr => '127.0.0.1',
PeerPort => 80,
Proto => 'tcp',
Timeout => 60 ) || die "fuck";
$sock->autoflush;
print $sock join("\015\012" =>
"GET / HTTP/1.0",
"Host: 127.0.0.1",
"User-Agent: xakep agent v 1.0",
"", "");
print <$sock>;
close ($sock);

Вот, собственно. Первое, что бы делаем это получаем новый объект IO::Socket::INET, который позволяет нам
работать с Интернет сокетами. Должен отметить, что кроме Интернет сокетов существуют еще и Юникс сокеты.
Но речь в данной статье пойдет только о первом виде сокетов - INET. Объекту IO::Socket::INET мы задали
несколько параметров:

PeerAddr - адрес хоста.
PeerPort - порт к которому мы конектимся.
Proto - протокол по которому конектимся.
Timeout - таймаут клиента.

После создания объекта мы включаем автоматическое очищение локального
буфера, в который записываются данные сокета с помощью метода autoflush. Страница, которая распечаталась после данного запроса,
содержит в себе заголовок страницы и само ее содержание. Если вам нужен только заголовок, то измените запрос GET на HEAD. Чтобы передать
данные скрипту с помощью POST запроса, немного видоизменим сам запрос.

print $sock join("\015\012" =>
"POST /cgi-bin/script.pl HTTP/1.0",
"Host: 127.0.0.1",
"User-Agent: xakep agent v 1.0",
"Content: par1=value1&par2=value2",
"Referer: xakep.ru",
"", "")
;

Поле Content в запросе позволяет передавать данные скрипту. Кроме того, в данном запросе мы установили
значение поля Referer. Теперь перейдем к самому интересному. Для начала осуществим коннект через прокси
сервер. Для этого вместо того, чтобы
коннектится к серверу, на который мы будем делать запрос,
приконектимся к прокси серверу, установив нужное значение PeerAddr. Скрипт должен будет выглядеть
примерно следующим образом.

require IO::Socket;
my $sock = IO::Socket::INET->new(PeerAddr => 'proxyadress',
PeerPort => 80,
Proto => 'tcp',
Timeout => 60 ) || die "fuck";
$sock->autoflush;
print $sock join("\015\012" =>
"GET / HTTP/1.0",
"Host: www.google.com",
"User-Agent: xakep agent v 1.0",
"", "");
print <$sock>;
close ($sock);

Смысл, я думаю, ясен. В самом запросе ничего не меняется, кроме поля Host, теперь значение этого не совпадает
со значение PeerAddr. В это поле нужно вставить адрес хоста на который делается запрос. Перейдем к более
интересным задачам. Если бы мы значение поля Host
установили равным "google.com", то получили примерно
следующий ответ:

HTTP/1.0 302 Found
...
Location: http://www.google.com/
...

А подобный ответ значит, что нам придется реализовывать редирект. Поле Location показывает куда нам нужно
сделать запрос. Давайте напишем небольшой скрипт для обработки редиректов.

my $res=get("google.com","80","/"); # вызываем подпрограмму get() с соответсвующими праметрами - хост, порт и путь
while ($res ne "exit") { #пока подрограмма не закончит свою работу продожаем обрабатывать редиректы
if ($res=~ m,^http://([^/:\@]+)(?::(\d+))?(/\S*)?$,)
{
$res=get($1,80,$3);
}
else { print "ERROR in url adress";
}
}
sub get {
require IO::Socket;
my $host=shift;
my $port=shift;
my $path=shift;
my $buf="";
my $sock = IO::Socket::INET->new( PeerAddr => $host, #создаем новый объект IO::Socket::INET
PeerPort => $port,
Proto => 'tcp',
Timeout => 60 ) || die "fuck";
$sock->autoflush;

print $sock join("\015\012" =>
"GET $path HTTP/1.0",
"Host: $host",
"User-Agent: xakep agent v 1.0",
"", "");
while (<$sock>) { $buf.=$_ } #считываем ответ сервера
if ($buf =~ m,^HTTP/\d+\.\d+\s+(\d+)[^\012]*\012,) { #получаем код шибки
my $code = $1;
if ($code =~ /^30[1237]/ && $buf =~ /\012Location:\s*(\S+)/) { #если ошибка вида 30х, значит
получим значение поля Location
my $url = $1;
print "redirected to $url\n\n";
close($sock);
return $url;

}
elsif($code=~/^200/) #если мы получаем 200 код, значит редирект окончен, завершаем программу.
{
print "done rederectig \n\n$buf";
close($sock);
return ("exit");
}
else { #если нам выдали код других ошибок (404,407,500...etc) завершаем программу
return ("exit");
close ($sock);
}}}

Я полагаю, что вам тут все должно быть понятно. Сначала мы проверяем ответ сервера. Если ответ
относится к типу 30x (redirected), мы пытаемся получить значение поля Location, в котом
содержится url редиректа. Если же ответ сервера - 200, значит редирект окончен и мы распечатываем страницу. Если же выдается
другая ошибка (404,500,407..etc), то скрипт просто прекращает работу.
Ничего нового мы здесь не придумывали, все достаточно просто и легко. Призываю вас не пугаться резкому увеличению объема кода. В сущности мы с вами
написали сейчас модуль LWP::Simple =) . Помните, я говорил про это в самом начале? Идем дальше. Нам осталось
реализовать лишь запрос используя безопасное соединение по https протоколу. Для этого воспользуемся модулем
IO::Socket::SSL. Вы всегда можете найти его на search.cpan.org. Для его работы также понадобится
OpenSSL. Вот небольшой пример того, как использовать данный модуль.

use IO::Socket::SSL;

my $sock = new IO::Socket::SSL("127.0.0.1:https") || warn "I encountered a problem: ", &IO::Socket::SSL::errstr();
print $sock join("\015\012" =>
"GET / HTTP/1.0",
"Host: 127.0.0.1",
"User-Agent: xakep agent v 1.0",
"", "");
print <$sock>;
close $sock;

Данный пример не нуждается в объяснениях, т.к. является почти полной аналогией предыдущих. Теперь, после
того, как мы разобрались как использовать модуль IO::Socket, предлагаю написать скрипт, который немного
обобщит эти знания. В качестве примера давайте напишем скрипт для накрутки счетчиков в жж
(www.livejournal.com). Для начала давайте найдем систему счетчиков. Первым что мне попалось стал сайт limbakh.spb.ru. Для того, чтобы
поднять пользователя жж в данной статистике нам понадобится: список прокси, имя пользователя жж, которого
будем поднимать в топе. Теперь важно понять, как работет счетчик, точнее как он должен работать. Пользователь
размещает код счетчика у себя на странице. Код
представляет собой тэг картинки <img>. В значении атрибута
src скрипту на сервере передаются данные о том, где этот счетчик расположен и таким образом сервер узнает
какому юзеру повышать рейтинг. Далее, когда посетитель заходит на страничку пользователя, его браузер обрабатывает
данный код и отсылает данные скрипту, который возвращает картинку с нарисованной на ней статистикой. Важно отметить,
что браузер также посылает строку Referer, к которой передается с какой страницы был загружен код. Теперь наша
задача - написать скрипт,  который будет эмулировать все это. Стоит заметить, что система статистики не
засчитывет запросы с одного ip адреса, поэтому воспользуемся прокси серверами. Итак скрипт:

open (DB,"db")||die "fuck"; # подгружаем базу прокси серверов.
while (<DB>) {
chomp;
my ($host,$port)=split(/:/,$_); # разбираем адрес текущего прокси сервера
print "trying $host:$port....";
get($host,$port); # обращаемся к подпрограмме, которая собственно и накручивает счетчик.
}
close DB;

sub get{

require IO::Socket;
my $host=shift;
my $port=shift;

#Cоздаем новый объект IO::Socket
my $sock = IO::Socket::INET->new(PeerAddr => $host,
PeerPort => $port,
Proto => 'tcp',
Timeout => 60 ) || goto(EXIT);
$sock->autoflush; #включаем режим автоматического очищения
буфера
#формеруем сам запрос
print $sock join("\015\012" =>
"GET /lj/lj.php?lj=ru_warez&id=4 HTTP/1.0",
"Host: limbakh.spb.ru",
"Referer: http://www.livejournal.com/~ru_warez",
"", "")||goto(EXIT);
print "done with $host:$port\n";
sleep(35); # чтобы не делать запросы с большой скоростью даем скрипту отдохнуть(время указываем в секундах).
EXIT:
if ($sock)
{
close ($sock);
}
else {print "\n"}
}

Вот и готов наш скрипт. Я специально выложил самое примитивное решение данной проблемы, чтобы дать вам возможность
усовершенствовать его самостоятельно. Идеи по усовершенствованию:

  1. Обрабатывайте ответ сервера, для того, чтобы удостовериться в том, что скрипт достиг своей цели. Ответ должен
    быть с кодом 200.

  2. Используйте генератор времени запроса. Идея состоит в следующем. Вы, путем случайной генерации чисел,
    получаете аргумент для функции sleep(), что понизит вероятность того, что вашу накрутку заметят. 

  3. Избавьтесь от функции goto() =)))

И напоследок хочу сказать, что все что вам нужно для того чтобы изучить подобные темы это мануал и
google.com... Ну и мозги разумеется. Удачи вам в ваших культурных и
интеллектуальных прорывах!

Check Also

Исходный кот. Как заставить нейронную сеть ошибиться

Нейросети теперь повсюду, и распознавание объектов на картинках — это одно из самых популя…

Оставить мнение