거실의 저소음 PC 팬이 밤새 굉음을 울린 이유
거실 구석에 놓인 저소음 홈시어터 PC의 냉각 팬이 밤새도록 최대 속도로 돌기 시작했습니다. 평소 4K 고화질 영화 스트리밍 전용으로만 쓰여 시스템 부하가 매우 낮던 기기였습니다. 기기 주인이 관리 화면에 접속해 확인해 보니, CPU 사용률이 며칠째 100%를 유지하고 있었습니다.
확인되지 않은 두 개의 백그라운드 프로세스가 연산 자원을 밤낮없이 소모하고 있었습니다. 하나는 네트워크 프록시로 위장한 myawesomeproxy였고, 다른 하나는 암호화폐 채굴 프로그램인 mylovelyminer였습니다. 주인은 출처가 불분명한 설치 파일을 내려받은 적이 없었으며, 관리자 화면에도 독립적이고 강력한 비밀번호를 설정해 둔 상태였습니다.
문제는 미디어 서버 소프트웨어인 Plex Media Server에 있었습니다. 보안 연구진은 plex2shell로 명명된 원격 코드 실행 체인(CVE-2026-96652, -96654, -96655, -96656)을 공개했습니다. 대상은 1.43.2 및 그 이전 버전입니다. 공용 인터넷에 노출된 공격자는 비밀번호를 전혀 몰라도 정교하게 조작된 요청을 보내 기기 전체의 제어권을 탈취할 수 있었습니다.
그림: 허니팟에 기록된 공격자의 자동화 스크립트 악성 페이로드 수집 과정. 출처: BoredHackerBlog.
검증되지 않은 콜백 주소를 악용해 서버 스스로를 공격하도록 유도
많은 로컬 네트워크 서비스에는 내부 루프백에서 오는 요청은 기본적으로 안전하다는 암묵적 가정이 존재합니다. 내부 통신의 검증 비용을 줄이기 위해 Plex는 로컬 요청에 대한 차단 규칙을 대폭 완화해 두었습니다. 이 신뢰 설계가 공격 체인의 돌파구가 되었습니다. 외부 공격자가 서버를 유도해 스스로를 공격하게 만들면 내부 방어선은 즉시 무너집니다.
첫 번째 빈틈은 재생 진행 상황을 보고받는 플레이어 타임라인 인터페이스 /player/timeline에 있었습니다. 규격상 X-Plex-Token 요청 헤더가 필요했지만, 서버 측 코드는 hasParam() 메서드만 호출하고 있었습니다. 이 메서드는 헤더의 존재 여부만 확인할 뿐 값의 유효성이나 인증 여부를 검증하지 않아 무의미한 임의의 문자열만 넣어도 통과할 수 있었습니다.
이 관문을 넘으면 서버는 플레이어 주소로 이벤트 구독 알림을 회신합니다. 내부 코드는 boost::format("%1%://%2%:%3%") 방식을 사용해 콜백 링크를 조합하고 있었으나, 프로토콜 파라미터 protocol에 대한 형식 검사나 문자 필터링이 전혀 이루어지지 않았습니다.
공격자는 이 파라미터에 루프백 주소가 포함된 완전한 URL을 전달했습니다. Plex는 즉시 자신의 내부 관리 포트로 HTTP POST 요청을 보냈습니다. 네트워크 전송 계층에서 요청 발신 IP는 실제 127.0.0.1이 되었고, 공격자는 신뢰받는 로컬 권한을 손에 넣었습니다.
문자열 접두사 검사 오류로 관리자 권한 토큰 유출
로컬 요청으로 위장한 공격자는 내부 플러그인 관리 인터페이스 /system/agents/*에 접근했습니다. 이 인터페이스는 요청 출처가 루프백 주소이면 인증을 건너뛰었습니다. 서버 스크립트 agentservice.py 내부에는 searchOne 함수에 접근 허용 목록이 구현되어 있었습니다.
하지만 함수 상단에 지정된 @lock 뮤텍스 데코레이터가 내부 함수 inner()의 반환값을 전달하지 않았습니다. 접근 제한 규칙에 의해 조기 종료가 발생해도 반환 상태가 데코레이터에 의해 무시되어 차단 메커니즘이 전혀 작동하지 않았습니다.
동시에 요청 파라미터 identifier의 필터링도 누락되어 있었습니다. 물음표를 넣으면 쿼리 파라미터를 추가할 수 있었고, 샵 기호를 넣으면 기본 /messaging/... 경로를 잘라낼 수 있었습니다. 이렇게 두 번째 결함이 연결되었습니다.
공격자는 이어 전체 관리자 권한 탈취를 노렸습니다. Plex 플러그인 프레임워크에는 127.0.0.1로 시작하는 주소로 나가는 외부 요청에 관리자 전용 local-* 인증 토큰을 자동으로 첨부해 주는 편의 기능이 있었습니다. 그러나 프레임워크가 호스트 이름 경계를 파싱하지 않고 단순 문자열 접두사 일치 검사만 수행했기 때문에, 127.0.0.1.evil.com 같은 주소도 검사를 통과했습니다.
공격자는 와일드카드 DNS 서비스 sslip.io를 활용해 127.0.0.1.10-0-0-9.sslip.io라는 도메인을 구성했습니다. 이 도메인은 접두사 규칙을 충족하면서도 공용 DNS 조회 시 공격자의 서버인 10.0.0.9로 연결됩니다. Plex는 규칙 검증을 통과했다고 판단하여 관리자 토큰을 공격자 서버로 직접 전송했고, 세 번째 취약점이 완성되었습니다.
그림: 보안 연구원이 수집한 외부 악성 IP의 취약점 탐색 및 악용 실시간 로그. 출처: BoredHackerBlog.
동영상 픽셀로 위장한 악성 코드, 트랜스코더를 악용해 시스템에 기록
관리자 토큰을 확보한 공격자는 서버 환경설정을 마음대로 수정할 권한을 갖게 되었습니다. Plex는 기기별 재생 지원을 위해 내장 트랜스코더를 활용합니다. 기본 x264 인코더 구성 요소에는 디버그 옵션이 포함되어 있었는데, 공격자는 여기에 디버그 지시어를 주입했습니다: TranscoderH264Options=dump-yuv=<경로>:qp=0.
dump-yuv 옵션은 내부에서 fopen(path, "w")를 호출하여 디코딩된 비디오 원본 프레임을 지정된 파일 경로에 직접 기록합니다. 양자화 파라미터 qp를 0으로 설정하면 무손실 모드가 활성화되어 영상 픽셀 값이 전혀 왜곡되지 않습니다. 공격자는 사전에 시스템 초기화 생성자 함수만 포함된 최소 크기의 ELF 동적 라이브러리(.so)를 컴파일해 두었습니다.
공격자는 컴파일된 바이너리 바이트를 MP4 비디오 첫 번째 프레임의 밝기 평면(Y 평면) 픽셀 값에 한 바이트씩 채워 넣었습니다. 겉보기에는 아무런 이상 없이 재생 가능한 H.264 캐리어 비디오가 완성되었습니다.
공격자는 관리자 토큰으로 트랜스코딩 요청을 보내며 path 파라미터가 자신의 원격 item.xml 재생 목록을 가리키도록 했습니다. Plex는 해당 캐리어 비디오를 가져와 트랜스코딩을 시작했습니다. 디코더가 첫 프레임을 디코딩하는 순간 디버그 기능이 실행되었고, 영상 픽셀 속에 숨겨져 있던 악성 동적 라이브러리가 트랜스코더 엔진에 의해 시스템 내부 Codecs 디렉터리에 그대로 저장되었습니다.
서명 검증 부재를 틈타 무단 동적 라이브러리가 권한 장악
악성 파일을 디스크에 저장하는 것은 절반에 불과하며, 시스템이 이를 실행하도록 만드는 것이 최종 목표입니다. Plex에 포함된 핵심 라이브러리 libavutil.so.58은 트랜스코더가 실행될 때마다 avpriv_load_new_libs 함수를 호출합니다. 이 함수는 Codecs 디렉터리 내의 모든 파일을 탐색하며, 파일 이름이 .so로 끝나기만 하면 무조건 dlopen(path, RTLD_NOW)를 실행했습니다.
이 로더 루틴에는 핵심적인 보안 절차가 빠져 있었습니다. 표준 접두사를 확인하지 않았고, 실행 권한 비트도 검사하지 않았으며, 디지털 서명 검증 절차도 없었습니다. 파일 권한이 일반 읽기 권한에 불과하더라도 그대로 메모리에 적재되었습니다.
ELF 동적 라이브러리 규격에 따라, dlopen은 유효한 심볼 내보내기 없이도 DT_INIT_ARRAY에 정의된 생성자 함수를 먼저 실행합니다. 생성자 함수 내부에서 fork()와 execve()를 호출해 즉시 역방향 셸을 열었습니다. 공격자는 이를 통해 데몬 프로세스 권한인 uid=1000(plex)를 직접 획득하며 전체 공격 체인을 마무리했습니다.
대형 언어 모델로 낮아진 취약점 체인 구축 장벽, 표적이 된 홈 엣지 기기
이번 취약점 발견 및 패치 과정은 매우 신속하게 진행되었습니다. 발견자는 2026년 7월 27일 공식 보고를 완료했고, Plex 팀은 8월 4일 이를 확인하고 포상금을 지급했습니다. 9월 23일 4개의 CVE 번호가 부여되었으며, 10월 16일 상세 내용과 패치가 공개되었습니다.
취약점 상세 정보가 공개된 직후 실제 환경에서의 악용 공격이 빠르게 포착되었습니다. 허니팟에는 45.194.92.222, 91.92.40.202, 176.65.139.228 등에서 발생한 자동화 탐색 트래픽이 기록되었으며, 공격자는 217.60.194.116을 통해 악성코드를 유포했습니다.
수집된 악성 샘플에는 해시값 5712ef510481f15fe8b18fdd10b73026d57cb3d9d151878a692358b99a464441의 컨테이너 이미지와 UPX로 압축된 바이너리 68938fb428f2bcfef4ede62f7b29cd0f15f37ed59e94462c099f2ca64e36baa7가 포함되어 있었습니다. 언팩 후 핵심 바이너리 해시값은 b20f39fc00d242e706b6c30367ad811c676e0575050a4ec2f30104b696944b49였습니다. 공격자들은 24시간 켜져 있는 홈 엣지 기기의 특성을 노려 프록시를 띄우고 은밀하게 암호화폐를 채굴했습니다.
취약점을 찾아낸 방식 역시 큰 관심을 끌고 있습니다. 발견자는 대형 언어 모델 기반의 워크플로를 활용했다고 밝혔습니다. 정찰, 분류, 검증, 연쇄 결합의 네 단계로 작업을 나누고, 독립된 에이전트를 통해 각 가설을 반증하도록 설계했습니다.
전문 분석가가 전체 아키텍처 판단과 공격 우선순위를 결정하고, 인공지능 도구가 복잡한 코드 분석 부담을 덜어주었습니다. 여러 개의 사소한 결함을 엮어 치명적인 공격 체인으로 만드는 비용이 크게 낮아졌으며, 전문적인 보안 방어가 부족한 가정용 엣지 기기가 그 첫 번째 표적이 되었습니다.
이 체인에는 복잡한 메모리 손상 기법이 전혀 포함되어 있지 않습니다. 검증되지 않은 헤더, 데코레이터가 삼켜버린 차단 지시, 경계 없는 접두사 매칭, 무제한 라이브러리 적재 등 각 단계는 개발 과정에서 흔히 발생하는 부주의였습니다.
로컬 네트워크 요청은 무조건 신뢰할 수 있다는 Plex의 설계 가정이 이 사소한 실수들을 결합하여 핵심 제어권으로 직행하는 통로를 열어주었습니다. 편의를 위해 생략한 인증 절차가 결국 가장 빠른 침투 경로가 된 셈입니다. 이미 자동화된 스캔이 진행되고 있는 만큼, 수정된 최신 버전으로 업데이트하여 기기가 채굴 도구로 전락하는 것을 막아야 합니다.
참고 링크:
- zmain.info의 plex2shell 분석 보고서
- BoredHackerBlog의 실제 악용 분석
- GitHub code-zm/plex2shell
- GitHub BoredHackerBlog/plex2shell_pcap
- Hacker News 토론 (item?id=50152321)