информационная безопасность
без паники и всерьез
 подробно о проекте
Rambler's Top100Страшный баг в WindowsВсе любят мед
BugTraq.Ru
Русский BugTraq
 Модель надежности двухузлового... 
 Специальные марковские модели надежности... 
 Модель надежности отказоустойчивой... 
 Facebook хранил часть пользовательских... 
 NSA выпустило Гидру 
 Неприятная уязвимость во всех WinRAR,... 
главная обзор RSN блог библиотека закон бред форум dnet о проекте
bugtraq.ru / библиотека / безопасность
БИБЛИОТЕКА
вход в библиотеку
книги
безопасность
программирование
криптография
internals
www
телефония
underground
беллетристика
разное
обзор: избранное
конкурс
рейтинг статей
обсуждение




Подписка:
BuqTraq: Обзор
RSN
БСК
Закон есть закон



The Bat!

Удаленное управление изолированными системами
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)  

[24122; 42; 7.14]




Rambler's Top100
Рейтинг@Mail.ru





  Copyright © 2001-2019 Dmitry Leonov   Page build time: 0 s   Design: Vadim Derkach