Сразу хочу предупредить, что при написании этой статьи преследовались только мирные цели. Средством разработки сознательно был выбран Perl, чтобы уменьшить вероятность использования в качестве трояна на Windows-системах (ну а юниксоиды и сами о себе позаботятся). Да и в мирных целях использовать описываемую программу лучше со всеми возможными предосторожностями.
Задача довольно обычная: получить возможность выполнить какие-либо команды на рабочем компьютере, находять при этом, например, дома. Причины могут быть самые разные: поставить ночную закачку гигазов варезов, перезагрузить RAS-сервер, да мало ли. Использование популярных средств удаленного управления, таких как PC Anywhere, RAdmin и т.п. при этом очень часто затруднено или из-за политики корпоративного файрволла, или вообще из-за использования в рабочей сети фиктивных адресов типа 192.168.0.1, которые снаружи недоступны.
По уму с этим лучше всего справиться установкой VPN либо хотя бы пробросом портов, но сами понимаете, не всегда и не у всех есть такая возможность. То есть считаем, что все, что у нас есть, так это рабочая машина, умеющая выходить наружу на веб, но изолированная от доступа снаружи, и желание ей воспользоваться. Посмотрим, что с этим можно сделать.
Самое очевидное, что приходит в голову при такой ситуации, это использовать промежуточный сервер. На самом деле, чтобы что-то заработало, достаточно выкладывать на бесплатный хостинг кусок батч-файла, и настроить на рабочем сервере другой батч-файл, который будет время от времени запускать wget, выкачивать новый батч и запускать его. Но такая схема работы выглядит не очень безопасно, да и не дает возможности узнать, удачно выполнились команды, или нет.
Поэтому, если есть возможность не ограничиваться халявным сервером без скриптов, лучше подойти к делу аккуратно и написать промежуточный CGI-скрипт (назовем его серверным модулем), который будет получать наши команды и запоминать их. Модуль на рабочей машине, назовем его исполнительным модулем, будет периодически обращаться к серверному модулю и получать текст очередной команды, которую надо выполнить. Дальше он вызывает эту команду, перехватывает результат ее работы и пересылает обратно на серверный модуль, который выкладывает его на веб-страницу.
Доступ к серверной программе, конечно, нужно обезопасить - паролем на доступ, шифрованием и т.п. В описанной программе я для простоты ограничился установкой паролей средствами сервера (например, через .htaccess в Apache).
Серверный модуль должен обрабатывать четыре команды. Выбор между ними делается с помощью параметра скрипта cmd:
Для удобства работы форму для получения команд и страницу с результатами лучше всего объединить в одну страницу с помощью фреймов. В итоге получается что-то похожее на telnet через веб, как на следующем рисунке.
Исполнительный модуль с заданной периодичностью опрашивает серверный, отправляя ему команду getcmd. При этом используется библиотека LWP, для доступа к запароленному каталогу добавляется заголовок Authorization.
Полученная команда отправляется на выполнение с помощью функции open(APP, "$cmd |"), позволяющей перехватить вывод консольной программы. Тут есть одна сложность. Запущенная команда может зависнуть из-за какой-нибудь ошибки, или если это оказалась оконная программа, дождаться завершения которой мы тоже не сможем. Чтобы не остаться из-за этого с повисшим сервером, выполнение команды запускается из дочернего процесса, запущенного функцией fork, а основной процесс исполнительного модуля сразу возвращается в цикл опроса. Результат работы команды опять пересылается на серверный модуль с помощью команды putres.
То, что у нас получилось - основной каркас, который можно наращивать по своему вкусу. Как видно из скриншота, в систему не мешает добавить конвертирование из DOS-кодировки (cp866), в которой выводятся результаты команд, в Windows-кодировку (cp1251), хотя для большинства задач это не так уж и важно. Для повышения безопасности стоит добавить шифрование пересылаемых данных. Кроме того, может понадобиться отдельная обработка команд типа set и cd, которые сейчас запускаются во внешнем процессе и никак не влияют на переменные окружения и текущий каталог исполнительного модуля. Все это, как говорится, выходит за рамки этой статьи :)
Серверный модуль
#!/usr/bin/perl use CGI; $datapath = $ENV{'DOCUMENT_ROOT'}."/data/"; $selfurl = $ENV{'REQUEST_URI'}; $refresh=10; $query = new CGI; $cmd = $query->param("cmd"); %MENU = ( show => \&show, run => \&run, getcmd => \&getcmd, putres => \&putres ); print $query->header(); dbmopen (%CMD, $datapath."cmd", 0666); dbmopen (%RES, $datapath."res", 0666); $MENU{$cmd}->() if(defined($MENU{$cmd})); dbmclose %RES; dbmclose %CMD; sub show { print "<html><head><meta http-equiv=refresh content=\"$refresh;url=$selfurl\"></head><body><pre>"; foreach my $t (sort {$b<=>$a} keys %RES) { my $res = $RES{$t}; $res =~ s/</</g; $res =~ s/>/>/g; print $res; } print "</pre></body></html>"; } sub run { $CMD{time()} = $query->param("command") if (length($query->param("command"))); print <<FORM; <form action=$selfurl method=POST> <input type=hidden name=cmd value=run> <input type=text name=command size=80> <input type=submit value=Enter> </form> FORM } sub getcmd { my $cmdcount = keys %CMD; if($cmdcount) { my $k = (sort {$a<=>$b} keys %CMD)[0]; print $CMD{$k}; delete $CMD{$k}; } } sub putres { %RES = (); $RES{time()} = $query->param("res"); }
Исполнительный модуль
#!/usr/bin/perl use LWP::UserAgent; use HTTP::Request; use HTTP::Response; use HTTP::Headers; use MIME::Base64; $url = 'http://yourserver.ru/cgi-bin/protected/server.cgi'; $name = 'user'; $pass = 'password'; $timeout=10; while(1) { my $cmd = getcmd(); run($cmd) if(length($cmd)); sleep($timeout); } sub getcmd { my $text = ''; my $ua = new LWP::UserAgent; $ua->agent("ProxyZilla 1.0"); my $req = new HTTP::Request(GET => "$url?cmd=getcmd"); my $author = MIME::Base64::encode_base64("$name:$pass"); $req->header(Authorization => "BASIC $author"); my $res = $ua->request($req); $text = $res->content if($res->is_success); return $text; } sub putcmd { my $res = shift; $res =~ s/([^a-zA-Z0-9_\-.])/uc sprintf("%%%02x",ord($1))/eg; my $ua = new LWP::UserAgent; $ua->agent("ProxyZilla 1.0"); my $req = new HTTP::Request(POST => $url); $req->content_type('application/x-www-form-urlencoded'); $req->content("cmd=putres&res=$res"); my $author = MIME::Base64::encode_base64("$name:$pass"); $req->header(Authorization => "BASIC $author"); $ua->request($req); } sub run { my($cmd) = shift; my $res = ""; unless(fork) { open(APP, "$cmd |"); $res .= $_ while(<APP>); close APP; print "$res\n"; putcmd ($res); exit; } }
Рабочий фреймсет
<frameset rows=*,50> <frame src="http://yourserver.ru/cgi-bin/protected/server.cgi?cmd=show"> <frame src="http://yourserver.ru/cgi-bin/protected/server.cgi?cmd=run"> </frameset>
обсудить | все отзывы (1) | |
[28235; 42; 7.14] |
|
|