В листе рассылки Full Disclosure появился скрипт, по заявлению автора, убивающий Apache начиная от самых старых версий до самых новых. И он действительно работает.

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

HEAD / HTTP/1.1
Host: сайт
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, закрывающие эту уязвимостью. Тем временем все сервера стоят открыты и не защищены.

Код для примера на php:

        $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 проксировать опасный заголовок можно директивой:`В листе рассылки Full Disclosure появился скрипт, по заявлению автора, убивающий Apache начиная от самых старых версий до самых новых. И он действительно работает.

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

HEAD / HTTP/1.1
Host: сайт
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, закрывающие эту уязвимостью. Тем временем все сервера стоят открыты и не защищены.

Код для примера на php:

        $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 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]

Данная статья предназначена не для хакинга, а для того, чтобы администраторы знали, какая есть дыра и как её закрыть. Использование дыры в любых целях может быть запрещено законодательством вашей страны, поэтому убедительно рекомендую не использовать данный скрипт.

Комментарии

comments powered by Disqus