Туннелирование запросов

Многие из рассмотренных нами атак контрабанды запросов возможны только потому, что одно и то же соединение между фронтонном и бэкэндом обслуживает несколько запросов. Хотя некоторые серверы переиспользуют соединение для любых запросов, у других политика строже.

Например, некоторые серверы разрешают переиспользование соединения только для запросов с одного и того же IP-адреса или одного и того же клиента. Другие вовсе не переиспользуют соединение, что ограничивает ваши возможности при классической контрабанде, поскольку у вас нет очевидного способа повлиять на трафик других пользователей.

no-connection-reuse.png

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

http-request-tunnelling.jpg

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

Туннелирование запросов на бэкэнд таким образом — более ограниченная форма контрабанды, но в умелых руках она всё ещё может приводить к эксплойтам высокой критичности.

Туннелирование запросов в HTTP/2

Туннелирование возможно как в HTTP/1, так и в HTTP/2, однако в средах только с HTTP/1 его значительно труднее обнаружить. Из-за особенностей keep-alive соединений в HTTP/1, даже если вы действительно получите два ответа, это не обязательно подтверждает, что запрос был успешно провезён.

В HTTP/2, напротив, каждый поток должен содержать только один запрос и один ответ. Если вы получаете ответ HTTP/2, в теле которого находится ответ HTTP/1, можно уверенно считать, что вы успешно протуннелировали второй запрос.

Утечка внутренних заголовков посредством туннелирования HTTP/2

Когда туннелирование — единственный вариант, вы не сможете раскрывать внутренние заголовки техникой, рассмотренной в одной из прошлых лабораторных работ, но понижение версии HTTP/2 открывает альтернативное решение.

Вы потенциально можете заставить фронтенд дописать внутренние заголовки внутрь того, что на бэкэнде станет параметром в теле запроса. Допустим, мы отправляем запрос, похожий на этот:

:method

POST

:path

/comment

:authority

vulnerable-website.com

content-type

application/x-www-form-urlencoded

foo

В этом случае и фронтенд, и бэкэнд сходятся во мнении, что здесь только один запрос. Интересно то, что их можно заставить не соглашаться в том, где заканчиваются заголовки. Фронтенд воспринимает всё внедрённое нами как часть заголовка, поэтому добавляет любые новые заголовки после завершающей строки comment=. Бэкэнд же видит последовательность \r\n\r\n и считает это концом заголовков. Строка comment=, вместе с внутренними заголовками, рассматривается как часть тела. В итоге получается параметр comment со значением из внутренних заголовков.

Слепое туннелирование запросов

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

Другие фронтенд-серверы считывают только то количество байтов, которое указано в заголовке Content-Length ответа, поэтому клиенту уходит только первый ответ. Это приводит к уязвимости слепого туннелирования, потому что вы не сможете увидеть ответ на свой протуннелированный запрос.

Неслепое туннелирование запросов с использованием HEAD

Слепое туннелирование трудно эксплуатировать, но иногда такие уязвимости можно сделать «неслепыми», используя запросы HEAD.

Ответы на HEAD часто содержат заголовок content-length, хотя собственного тела у них нет. Обычно он относится к длине ресурса, который вернул бы запрос GET к той же конечной точке. Некоторые фронтенды не учитывают это и всё равно пытаются считать указанное в заголовке число байтов. Если вы успешно протуннелировали запрос мимо такого фронтенда, это поведение может привести к чрезмерному чтению ответа от бэкэнда. В результате вы получите ответ, который содержит байты начала ответа на ваш протуннелированный запрос.

Request

:method

HEAD

:path

/example

:authority

vulnerable-website.com

foo

Response

:status

200

content-type

text/html

content-length

131

Поскольку вы по сути смешиваете заголовок content-length из одного ответа с телом другого, успешное применение этой техники — вопрос тонкой подстройки.

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

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

  • Направьте запрос HEAD на другую конечную точку, которая возвращает более длинный или более короткий ресурс — в зависимости от нужд.

  • Если ресурс слишком короткий, используйте отраженный ввод в основном запросе HEAD для вставки произвольных символов заполнения. Даже если вы не увидите отражение своего ввода, возвращаемый content-length все равно увеличится соответственно.

  • Если ресурс слишком длинный, используйте отражаемый ввод в протуннелированном запросе, чтобы внедрить произвольные символы так, чтобы длина протуннелированного ответа соответствовала или превышала ожидаемую длину содержимого.

Отравление веб-кэша через туннелирование HTTP/2

Хотя туннелирование в целом более ограничено, чем классическая контрабанда, иногда вы всё же можете построить атаки высокой критичности. Например, можно комбинировать рассмотренные техники туннелирования для довольно эффективного отравления веб-кэша.

При неслепом туннелировании вы фактически можете смешивать заголовки одного ответа с телом другого. Если тело содержит неэкранированный пользовательский ввод, вы можете использовать это поведение для отражённого XSS в контекстах, где браузер обычно не исполняет код.

Например, следующий ответ содержит неэкранированные данные, контролируемые атакующим:

Само по себе это относительно безвредно. Из-за Content-Type эта полезная нагрузка будет интерпретирована браузером как JSON. Но подумайте, что произойдёт, если вместо этого вы протуннелируете запрос на бэкэнд. Этот ответ появится внутри тела другого ответа, фактически унаследовав его заголовки, включая content-type.

:status

200

content-type

text/html

content-length

174

Поскольку кэширование происходит на фронтенде, кэши также можно обмануть, заставив их раздавать эти смешанные ответы другим пользователям.

Last updated