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

Преамбула

Посчастливилось мне обучаться пентесту. Первый этап обучения (начальная подготовка) прошел в декабре 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 еще нечто уникальное. Защититься же от скачивания вебинаров, к которым студенты имеют доступ, сложнее. Но, насколько я знаю, эта защита также усилена.