Не все cookie одинаково полезны

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

История началась больше года назад, когда я испытывал программу DOMinator для поиска DOM-Based XSS на сайтах Bug Bounty программ. Одним из первых предупреждений, которое я получил, была уязвимость Cookie Injection в JavaScript сценарии Google Analytics.

При обращении к сайту с Google Analytics, сценарий обрабатывает значение HTTP заголовка Referer и извлекает из него host и путь к сценарию, для отслеживания откуда пришел пользователь. В дальнейшем эти данные попадают в cookie параметр __utmz.

Выглядит это следующим образом:
__utmz=123.123.11.2.utmcsr=[HOST]|utmccn=(referral)|utmcmd=referral|utmcct=[PATH]

Изменяя путь к сценарию на своем сайте, с которого пользователь переходит на сайт с Google Analytics, можно влиять на конец значения cookie __utmz и попытаться изменить атрибуты cookie, так как путь никак не обрабатывается перед попаданием в значение. Однако, атрибуты будут перезаписаны последующими значениями, которые подставляет Google Analytics.

http://blackfan.ru/x/injection;injection=injection?r=http://site.com/

Результат:
document.cookie=__utmz=blah...|utmcct=/x/injection;injection=injection; path=/; domain=.site.com

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

Bug Bounty и Cookie


Небольшое отступление. Уязвимости, связанные с cookie, в рамках программ вознаграждения за уязвимости, примечательны тем, что у вас может получится два основных сценария:

  • Найдена возможность перезаписи и создания произвольных cookie параметров.
    Ответ: Это всего лишь cookie, что это тебе даст?
  • Найдена XSS через cookie параметр.
    Ответ: Ты ведь не можешь создать произвольный cookie параметр, значит, это не уязвимость!


И только если оба варианта оказались в рамках одной Bug Bounty программы, то вам, так уж и быть, заплатят.

Необходимость каждый раз доказывать угрозу от проблем, связанных с сookie, зачастую отталкивает людей от их поиска. Так как за потраченное на это время можно найти еще десяток уязвимостей, которые будут приняты без вопросов. В основном проверяются только самые базовые вещи, типа фиксации сессии и правильности установленных атрибутов secure, httpOnly…

Cookie


Взглянем на структуру сookie заголовков:

Set-Cookie: par=val; path=/; httpOnly; secure;
Cookie: par=val; par2="val2"; par3=val3;

Структура довольно сложная и с ней работают при каждом запросе браузер, JavaScript сценарии и веб-сервер. Каждый при этом обрабатывает по-своему, и одна и та же строка может дать разные результаты.

Анализируя обработку cookie необходимо задаться следующими вопросами:

  1. Нужны ли пробелы после;
  2. Какие символы можно использовать вместо ;
  3. Какое значение будет результирующее в случае одинаковых ключей
  4. Важен ли регистр у ключей
  5. Сколько может быть атрибутов у параметра
  6. Какое значение будет результирующее в случае одинаковых атрибутов
  7. Как правильно кодировать спецсимволы


Особенности обработки Cookie


Первая и наиболее известная особенность — Safari позволяет объявлять несколько параметров через один заголовок Set-Cookie.

Set-Cookie: param1=value1; path=/, param2=value2; httpOnly;

Возвращаясь к проблеме Google Analytics, проверим данную особенность в установке cookie через JavaScript. Получаем первый вариант эксплуатации:

http://blackfan.ru/x/injection;,injection_cookie=injection;?r=http://site.com/

Результат
document.cookie=__utmz=blah...|utmcct=/r/injection;,injection_cookie=injection; path=/; domain=.site.com

Safari создаст два cookie параметра __utmz и injection_cookie.

То есть, для пользователя Safari на любом сайте с Google Analytics можно создать произвольный cookie параметр. Осталось только придумать зачем…

CSRF


Защиту от CSRF можно условно разделить на 3 типа:

  1. Различные токены для каждого действия. Хранятся на сервере.
  2. Один сессионный токен на все действия. Хранится на сервере в сессии пользователя.
  3. Один сессионный токен на все действия. Хранится в cookie параметре.


Третий вариант основывается на том, что значение токена в cookie пользователя недоступно для злоумышленника. Для прохождения проверки достаточно просто послать одинаковое значение токена в cookie и post параметрах. То есть, простая перезапись значения через Google Analytics тут подойдет идеально.

Особенности обработки Cookie #2


Что если развернуть атаку на 180 градусов? Не обязательно, чтобы в браузере действительно был cookie параметр с CSRF токеном, значение которого мы знаем. Достаточно, чтобы так считал веб-сервер.

В этом поможет еще одна обнаруженная особенность обработки сookie.

RFC2109
Note: For backward compatibility, the separator in the Cookie header
is semi-colon (;) everywhere. A server should also accept comma (,)
as the separator between cookie-values for future compatibility.


Многие веб-серверы поддерживают перечисление сookie не только через точку с запятой, но и через запятую.

Cookie: par=val; par2="val2"; par3=val3;
Cookie: par=val, par2="val2", par3=val3,

Более того, в некоторых случаях пробел не обязателен.

Cookie: par=val;par2="val2";par3=val3;
Cookie: par=val,par2="val2",par3=val3,

В свою очередь, для большинства браузеров такие символы как пробел и запятая являются вполне нормальными. И если установить:

Set-Cookie: par=val, csrftoken=val2;
document.cookie="par=val, csrftoken=val2;";

Для браузера это будет одно значение, но для некоторых серверных реализаций — два сookie параметра. Однако, в случае эксплуатации данных особенностей для обхода CSRF защиты, необходимо помнить, что мы не перезаписываем старый токен, а добавляем еще один, то есть важен порядок обработки сookie.

Cookie: csrftoken=realvalue; par=val, csrftoken=fakevalue;

Итого получается уже две цепочки эксплуатации уязвимости:

Safari -> WebApp (GA & Double Submit Cookies)

Любой браузер -> WebApp (GA & Double Submit Cookies & Запятая, как разделитель cookie)

Эксплуатация


После подготовки неплохой базы, необходима проверка в реальных условиях. Практически сразу находится идеальный вариант mobile.twitter.com.

На нем реализована CSRF защита, основанная на cookie, сервер поддерживает перечисление cookie через запятую без пробела, но… На нем нет Google Analytics. Зато он есть на translate.twitter.com! Самое время проверить обработку атрибутов cookie значений, а если точнее, проверить возможность отбрасывания добавляемых в конце значений path и domain в случае, если инъекция происходит в значение cookie.

Оказалось, что Google Chrome в случае большого количества атрибутов cookie в какой-то момент просто прекращает их разбирать и не доходит до последних валидных значений.

То есть, в данном случае:

Set-Cookie: test=test; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; [...]; domain=blah.blah.blah.google.com;

Cookie будет установлена на .google.com, а не на blah.blah.blah.google.com.

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

Chrome -> WebApp 1 (Double Submit Cookies & Запятая, как разделитель cookie) & WebApp 2 (GA)

Формируем PoC:

  
    
      
      
    
    Tweet "PoC"
      
  










  
  


Описание:

  1. Пользователь авторизован на twitter.com
  2. Мобильная версия mobile.twitter.com подхватывает сессию основного сайта даже если пользователь не заходил на нее
  3. Предполагаем, что пользователь не был на translate.twitter.com и у него не установлена cookie __utmz на нем
  4. Передаем Referer с путем, в котором содержится инъекция в cookie, на сайт translate.twitter.com
  5. Google Analytics создает сookie __utmz=blah...|,m5_csrf_tkn=x,
  6. Из-за большого количества атрибутов Chrome перезаписывает domain на .twitter.com
  7. Ждем окончание обработки запроса и посылаем еще один запрос на создание твита «PoC», используя токен «x»
  8. В соответствии с порядком отправленных cookie, mobile.twitter.com берет наше значение токена «x» и убеждается, что значение в post запросе и в cookie одинаковые
  9. У пользователя появляется твит PoC


Особенности обработки Cookie #3


Следующей целью стал instagram.com, а точнее все сайты на Django. CSRF защита в Django также основана на cookie. Для успешного прохождения проверки достаточно послать одинаковые значения в cookie csrftoken и post параметре csrfmiddlewaretoken, либо в HTTP заголовке X-CSRFToken. Однако, есть дополнительная проверка, которая в дальнейшем может помешать. В случае, если сайт работает по HTTPS, Django проверяет заголовок Referer и, в случае несовпадения, блокирует запрос, даже если в нем указан правильный токен. Post запросы без Referer также блокируются.

В ходе изучения обработки сookie в Django были обнаружены следующие особенности:

  1. В качестве разделителя не обязательно использовать точку с запятой, достаточно любого пробельного символа между параметрами
    Cookie: test=test test2=test2
  2. Если в значении сookie есть символы [ \ ], то первая часть сookie отбрасывается
    Cookie: test=test]test2=test2.
    В результате создается только сookie test2.


Оказалось, что данная проблема даже не в Django, а в Python. Библиотека Cookie обрабатывает значения в соответствии с RFC2109. И действительно оказывается, что использование символов [ \ ] не предусмотрено, если значение не обрамлено двойными кавычками. Для браузеров же использование этих символов — вполне нормальное явление.

PoC для instagram практически идентичен предыдущему: