Буквально пять дней назад в листе рассылки Full Disclosure появился скрипт, по заявлению автора, убивающий Apache начиная от самых старых версий до самых новых.
И он действительно работает. Скрипт killapache.pl запускает в несколько десятков потоков простой запроc:
HEAD / HTTP/1.1Host: сайтRange: bytes=0-,5-0,5-1,5-2,5-3,5-4,<...>,5-1299,5-1300Accept-Encoding: gzipConnection: close
В ответ на такой запрос Apache для подсчета Content-Length собирает в памяти длинный ответ из перекрывающихся кусков запрошенного файла, который может занять и занимает заначительный объём памяти. При этом потрябление памяти Apache начинает резко расти, как на том графике в начале, что при должном, совсем небольшом, количестве запросов приводит к DoS.
Разработчики Apache подошли к этой проблеме серьёзно, инициативные лица уже предложили изменения в RFC, закрывающие эту уязвимостью. Тем временем все сервера стоят открыты и не защищены.
Код для теста
$port = 80;$fp = stream_socket_client('tcp://gov.ru'.':'.$port, $err, $errstr, 8, STREAM_CLIENT_CONNECT);if (!$fp) echo 'error in socket';else{$query="GET http://gov.ru/robots.txt HTTP/1.0\r\n"."Range: bytes=0-1,0-2"."Accept-Encoding: gzip\r\n"."Connection: close\r\n\r\n";fwrite($fp, $query);}$res = array();$res['status']='ok';$res['data'] = '';$count_read=0;while (!feof($fp)){unset($query);$res['data'] .= fgets($fp, 4096);$count_read++;if($count_read>1) {if($status['unread_bytes']==0){$res['status'] = 'failed';break;}else{}}else{}$status=socket_get_status($fp);}print_r($res);fclose($fp);
Как быть?
Если у вас перед Apache стоит nginx, то можно вообще ничего не делать, даже если файлы, для которых возможны описанные выше запросы, не раздаёт nginx, потому как по-умолчанию, по крайней мере на версии 1.1.0, nginx не передаёт загловок Range на проксируемый сервер.
Для проверки своего сайта возьмите приведённый сверху код и подставьте адрес сайта своего. Если на такие запросы отвечает Apache и вы видите 206 Partial Content, значит быть беде.
Запретить nginx проксировать опасный заголовок можно директивой:proxy_set_header Range "";
Если нет nginx?
Если у вас во внешний мир Apache смотрит напрямую… Вы можете полностью заблокировать проблемный заголовок при помощи mod_headers:RequestHeader unset Range
Если же вам всё-таки нужен этот заголовок, существует решение на основе mod_rewrite. Длительные последовательности Range можно блокировать через .htaccess:# Вариант 1:RewriteEngine OnRewriteCond %{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 OnRewriteCond %{REQUEST_METHOD} ^(HEAD|GET) [NC]RewriteCond %{HTTP:Range} ([0-9]*-[0-9]*)(\s*,\s*[0-9]*-[0-9]*)+RewriteRule .* - [F]# Вариант 3:RewriteEngine OnRewriteCond %{HTTP:Range} bytes=0-.* [NC]RewriteRule .? http://%{SERVER_NAME}/ [R=302,L]
Часть материалов взята с Хабра