20.08.2011 в
листе рассылки Full Disclosure появился
скрипт, по заявлению автора, убивающий Apache начиная от самых старых
версий до самых новых.
И он действительно работает.
Скрипт 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 даже на приличных серверах.
Разработчики Apache
подошли к этой проблеме серьёзно, инициативные лица уже предложили
изменения в RFC, закрывающие эту уязвимостью. Тем временем все сервера стоят открыты и не защищены.
Как же быть?
Если у вас перед Apache стоит nginx, то можно вообще ничего не делать,
даже если файлы, для которых возможны описанные выше запросы, не раздаёт
nginx, потому как по-умолчанию, по крайней мере на версии 1.1.0, nginx
не передаёт загловок
Range или
Request-Range
на проксируемый сервер.
Проверить, уязвим ли ваш сервер к этой атаке легко:
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, значит быть беде.
Запретить nginx проксировать опасный заголовок можно директивой:
proxy_set_header Range "";
proxy_set_header Request-Range "";
Внимание! После этого на вашем сайте отключится режим докачки файлов
Если нет nginx?
Если у вас во внешний мир Apache смотрит напрямую… Вы можете полностью заблокировать проблемный заголовок при помощи
mod_headers
:
a2enmod headers
echo "RequestHeader unset Range" > /etc/apache2/conf.d/security.conf
echo "RequestHeader unset Request-Range" >> /etc/apache2/conf.d/security.conf
/etc/init.d/apache2 restart
Если же вам всё-таки нужен этот заголовок, существует
решение на основе mod_rewrite и ещё несколько более сложных с mod_headers.
Длительные последовательности Range можно блокировать через .htaccess (
рецепт взят отсюда):
# Вариант 1:
RewriteEngine On
RewriteCond %{HTTP:Range} bytes=0-[0-9]+, [NC,OR]
RewriteCond %{HTTP:Range} bytes=([0-9-],){4,} [NC,OR]
RewriteCond %{HTTP:Range} bytes=[0-9,-]+,0-(,|$) [NC]
RewriteRule .? http://%{SERVER_NAME}/ [NS,L,F]
# Вариант 2:
RewriteEngine On
RewriteCond %{REQUEST_METHOD} ^(HEAD|GET) [NC]
RewriteCond %{HTTP:Range} ([0-9]*-[0-9]*)(\s*,\s*[0-9]*-[0-9]*)+
RewriteRule .* - [F]
# Вариант 3:
RewriteEngine On
RewriteCond %{HTTP:Range} bytes=0-.* [NC]
RewriteRule .? http://%{SERVER_NAME}/ [R=302,L]
Официальный источник дает такое решение для Apache 2.2:
SetEnvIf Range (?:,.*?){5,5} bad-range=1
RequestHeader unset Range env=bad-range
RequestHeader unset Request-Range