BugTraq.Ru
Русский BugTraq
https://bugtraq.ru/library/security/proxyfix.html

Удаленное управление изолированными системами
Parallax
Опубликовано: dl, 06.11.06 04:38

Введение или disclaimer

Сразу хочу предупредить, что при написании этой статьи преследовались только мирные цели. Средством разработки сознательно был выбран Perl, чтобы уменьшить вероятность использования в качестве трояна на Windows-системах (ну а юниксоиды и сами о себе позаботятся). Да и в мирных целях использовать описываемую программу лучше со всеми возможными предосторожностями.

Задача

Задача довольно обычная: получить возможность выполнить какие-либо команды на рабочем компьютере, находять при этом, например, дома. Причины могут быть самые разные: поставить ночную закачку гигазов варезов, перезагрузить RAS-сервер, да мало ли. Использование популярных средств удаленного управления, таких как PC Anywhere, RAdmin и т.п. при этом очень часто затруднено или из-за политики корпоративного файрволла, или вообще из-за использования в рабочей сети фиктивных адресов типа 192.168.0.1, которые снаружи недоступны.

По уму с этим лучше всего справиться установкой VPN либо хотя бы пробросом портов, но сами понимаете, не всегда и не у всех есть такая возможность. То есть считаем, что все, что у нас есть, так это рабочая машина, умеющая выходить наружу на веб, но изолированная от доступа снаружи, и желание ей воспользоваться. Посмотрим, что с этим можно сделать.

Решение

Самое очевидное, что приходит в голову при такой ситуации, это использовать промежуточный сервер. На самом деле, чтобы что-то заработало, достаточно выкладывать на бесплатный хостинг кусок батч-файла, и настроить на рабочем сервере другой батч-файл, который будет время от времени запускать wget, выкачивать новый батч и запускать его. Но такая схема работы выглядит не очень безопасно, да и не дает возможности узнать, удачно выполнились команды, или нет.

Поэтому, если есть возможность не ограничиваться халявным сервером без скриптов, лучше подойти к делу аккуратно и написать промежуточный CGI-скрипт (назовем его серверным модулем), который будет получать наши команды и запоминать их. Модуль на рабочей машине, назовем его исполнительным модулем, будет периодически обращаться к серверному модулю и получать текст очередной команды, которую надо выполнить. Дальше он вызывает эту команду, перехватывает результат ее работы и пересылает обратно на серверный модуль, который выкладывает его на веб-страницу.

Доступ к серверной программе, конечно, нужно обезопасить - паролем на доступ, шифрованием и т.п. В описанной программе я для простоты ограничился установкой паролей средствами сервера (например, через .htaccess в Apache).

Детали реализации

Серверный модуль должен обрабатывать четыре команды. Выбор между ними делается с помощью параметра скрипта cmd:

  1. run - запоминает команду в базе (для простоты в программе используются dbm-файлы, ключ - текущее время) и выводит форму для ввода команды.
  2. getcmd - обрабатывает запрос от исполнительного модуля, выводит текст последней команды и удаляет ее из базы.
  3. putres - получает результат работы от исполнительного модуля и складывает ее в базу. Для упрощения кода сохраняется только последний результат, а вообще вместо %RES = () можете вставить очистку по таймеру или по количеству.
  4. show - печатает страницу с результатами, для ее периодического обновления используется тег meta http-equiv=refresh.

Для удобства работы форму для получения команд и страницу с результатами лучше всего объединить в одну страницу с помощью фреймов. В итоге получается что-то похожее на 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/</&lt;/g;
		$res =~ s/>/&gt;/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)

[27982; 42; 7.14]




  Copyright © 2001-2024 Dmitry Leonov Design: Vadim Derkach