FFmpeg의 스물한 개 제로데이
요약
미디어 처리 라이브러리 FFmpeg에서 보안 에이전트가 21개의 제로데이 취약점을 발견했습니다. 이 에이전트는 대규모 C 코드베이스를 깊게 분석하여 재현 가능한 구체 입력으로 실행 확인된 취약점들을 찾아냈습니다. 이는 기존의 이론적 경고 방식보다 실질적인 보안 검증에 훨씬 효과적임을 보여줍니다.
핵심 포인트
- FFmpeg는 미디어 처리 과정에서 중요한 공격 표면입니다.
- 보안 에이전트는 대규모 C 코드베이스의 숨겨진 취약점을 발굴할 수 있습니다.
- 발견된 21개 제로데이는 재현 가능한 PoC를 통해 검증되었습니다.
- 에이전트 기반 분석은 기존 상용 모델 대비 비용 효율적입니다.
- 전 세계 브라우저와 스트리밍 인프라에서 미디어를 처리하는
FFmpeg는 복잡하고 신뢰할 수 없는 입력을 파싱해 보안상 중요한 공격 표면이 됨 - depthfirst의 자율
보안 에이전트는 약 150만 줄의 최적화된 C 코드에서 21개 제로데이를 찾았고, 비용은 약 $1k로 Anthropic의 Mythos 사용 비용 $10k의 10% 수준이었음 - 발견 항목은 TS demuxer, VP9 decoder, RTP/RTSP/RTMP 처리 경로 등 여러 구성요소에 걸쳐 있으며, 일부 취약점은
15~20년 동안 잠재 상태였음 - AV1 RTP depacketizer 취약점은 183바이트 RTP 패킷만으로
함수 포인터를 덮어쓰는 PoC로 이어졌고,ffmpeg -i rtsp://attacker/stream
실행만으로 도달 가능함
- 실질적 보안 검증은 이론적 경고가 아니라 재현 가능한 입력과 실행 확인까지 필요하며, 에이전트형 분석은 대형 C 코드베이스의 숨은 취약점 발굴에 직접 활용될 수 있음
개요
FFmpeg는 브라우저와 대형 스트리밍 플랫폼 인프라 등에서 미디어를 처리하는 널리 배포된 소프트웨어임
- 복잡하고 신뢰할 수 없는 미디어를 지속적으로 파싱하는 라이브러리이기 때문에 보안상 중요하며, 제로클릭 공격의 주요 표적이 됨
- FFmpeg 저장소는 약
150만 줄의 고도로 최적화된 C 코드로 구성되고, 수백 개의 복잡한 미디어 형식을 파싱함 - FFmpeg는 20년 넘게 퍼징과 수동 감사를 받아왔으며, 최근 Google Big Sleep 팀은 FFmpeg에서 13개 취약점을 공개함
- Anthropic은 Mythos 모델로 FFmpeg를 스캔해 보안 이슈 일부를 발견함
- 이러한 선행 작업 이후 FFmpeg에서 새 취약점을 찾기는 더 어려워졌고, 대형 코드베이스를 깊게 스캔하는 에이전트형 시스템의 역량을 확인하는 대상이 됨
Depthfirst의 보안 에이전트
- 코딩 에이전트와 보안 에이전트는 같은 기반 모델을 사용할 수 있지만, 목표가 크게 다름
코딩 에이전트는 보통 사람의 과제를 받아 애플리케이션 코드를 작성하는 데 초점을 둠
보안 에이전트는 구체적 지시 없이 기존 시스템에서 실제 악용 가능한 보안 이슈를 찾는 좁고 목표 지향적인 역할을 맡음 - 보안 에이전트는 코드베이스의 아키텍처, 노출된 파서와 프로토콜 핸들러, 공격자 제어 입력의 진입 지점을 먼저 파악해야 함
- 이후 저장소를 평면적인 파일 묶음으로 다루지 않고, 공격 표면 코드에서 관련 구성요소를 따라
데이터 흐름을 추적함 - 실용적인 보안 에이전트에는 누락된 조건을 꾸며내거나, 이론적 버그를 과장하거나, 오탐을 대량으로 내지 못하게 하는 가드레일이 필요함
- 공격자가 실제로 올바른 입력을 제어하는지, 취약 경로에 도달 가능한지, 의심되는 결함이 재현 가능한지 확인해야 함
- 필요한 경우 대상 구성요소와 상호작용하는
하네스(harness) 를 식별하거나 생성해 가설을 구체적으로 시험해야 함 - depthfirst의 특화 보안 에이전트는 코드를 깊게 분석하고, 여러 가설을 병렬로 분기해 시험함
- 결과물은 이론적 보고서나 모호한 경고가 아니라, 재현 가능한 구체 입력으로 실행 확인된 보안 이슈가 됨
발견 결과
- 에이전트는 총
21개 제로데이를 발견했고, 범위는 TS demuxer부터 VP9 decoder까지 걸쳐 있음 - 전체 비용은 약
$1k였고, 이는 Anthropic이 Mythos 사용에 쓴 비용의 10% 수준임 - 비용 비교:
CVE가 할당된 취약점
CVE-2026-39210은 TS demuxer에서 두 바이트를 읽기 전 길이 경계 검사가 빠진 힙 버퍼 오버플로이며, 2010년에 들어감
CVE-2026-39211은 swscale 리팩터링 중 상한 없는 크기 계수 공식으로 사용자 제어 매개변수가 임의로 큰 스케일링을 유발할 수 있었던 정수 오버플로이며, 2010년에 들어감
CVE-2026-39212는 ffmpeg_opt.c
에서 프리셋 파일이 깊이 제한 없이 옵션 파싱을 재귀적으로 트리거할 수 있었던 스택 오버플로이며, 2025년 7월 회귀로 들어감
CVE-2026-39213은 yuv4mpegenc rawvideo 입력 경로에서 패킷 크기에 대한 차원 검증이 빠진 힙 버퍼 오버플로이며, 2023년에 들어감
CVE-2026-39214는 원래 SDT 구현에서 남은 공간을 추적하지 않고 서비스 엔트리를 쓰는 스택 버퍼 오버플로이며, 2003년에 들어가 23년 동안 잠재 상태였음
CVE-2026-39215는 update_mb_info()
안의 로직 오류로 이후 호출이 할당 버퍼 뒤 12바이트를 쓰게 되는 힙 버퍼 오버플로이며, 2012년에 들어감
CVE-2026-39216은 img2enc.c
에서 안전한 크로마 크기를 차원 기반 무제한 크기로 대체해 생긴 힙 버퍼 오버플로이며, 2012년에 들어감
CVE-2026-39217은 VP9 decoder에서 리팩터링된 크기 업데이트 함수 때문에 타일 스레드 버퍼가 필요한 재할당을 놓치는 힙 버퍼 오버플로이며, 2025년 3월 회귀로 들어감
CVE-2026-39218은 DASH demuxer에서 음수 duration 값을 거부하지 않아 fragment 배열 인덱스가 음수가 되는 힙 버퍼 오버플로이며, 2017년에 들어감
내부 추적 ID로 참조된 취약점
DFVULN-127은 RTP AV1 depacketizer의 av1_handle_packet()
이 Temporal Delimiter OBU를 건너뛰며 obu_size
만큼 출력 위치를 전진시키지만 같은 크기의 공간을 할당하지 않아 다음 OBU가 버퍼 경계 밖에 쓰이는 힙 버퍼 오버플로임
DFVULN-126은 swscale graph 코드의 run_legacy_unscaled()
가 interlaced YUV420P→NV12 변환을 잘못 처리해 대상 Y-plane을 576바이트 넘겨 쓰는 힙 버퍼 오버플로임
DFVULN-125는 RTP JPEG depacketizer의 jpeg_create_header()
가 1024바이트 스택 버퍼에 quantization-table 섹션을 만들 때, qtable_len >= 1024
패킷 뒤의 AV_WB16
이 끝을 2바이트 넘겨 쓰는 스택 버퍼 오버플로임
DFVULN-124는 AVIF overlay 경로의 istg_parse_tile_grid()
가 타일 엔트리 0개인 dimg
참조를 거부하지 않아 부호 없는 래핑 뒤 1바이트 힙 할당에서 범위 밖 읽기를 일으키는 힙 버퍼 오버플로임
DFVULN-123은 RTP LATM depacketizer의 latm_parse_packet()
이 signed 32-bit 덧셈 오버플로로 경계 검사를 우회해 memcpy
가 힙 버퍼 끝에서 약 1GB 뒤를 읽게 하는 정수 오버플로임
DFVULN-122는 RTP MPEG-4 depacketizer의 aac_parse_packet()
이 AU-headers-length 0을 받아 1바이트 할당을 만든 뒤 AU header 존재 확인 없이 4바이트 필드로 읽는 힙 버퍼 오버플로임
DFVULN-121은 CAF demuxer의 read_seek()
가 av_index_search_timestamp()
의 반환값 -1을 검사하지 않고 배열 인덱스로 사용해 index_entries[-1]
에 접근하는 힙 버퍼 언더플로임
DFVULN-120은 AVI demuxer의 ff_read_riff_info()
가 size >= 4
확인 없이 size - 4
를 받아 LIST chunk 크기 0에서 약 4GB로 언더플로하고 약 2GB 할당을 유발하는 정수 언더플로임
DFVULN-119는 option parser의 opt_map()
에 있는 불필요한 증가가 link-label을 file index로 잘못 파싱하고 stream index -1을 저장해 이후 루프가 AVStream**
배열 앞을 읽게 하는 힙 버퍼 오버플로임
DFVULN-118은 RTSP server 경로의 rtsp_read_announce()
가 음수 Content-Length
를 유효하게 처리해 원격 ANNOUNCE
와 Content-Length: -1
로 sdp[-1]
에 범위 밖 쓰기를 일으키는 힙 버퍼 오버플로임
DFVULN-117은 RTMP client의 rtmp_calc_swfhash()
가 in_size < 8
대신 in_size < 3
만 검사해 최소 3바이트 버퍼에서 8바이트를 읽는 힙 버퍼 오버플로임
DFVULN-116은 RTSP SDP parsing의 sdp_parse_line()
이 빈 문자열에서 strlen(control_url) - 1
을 계산해 size_t
가 SIZE_MAX
로 래핑되고 1바이트 pre-buffer read가 발생하는 힙 버퍼 오버플로임
건너뛴 프레임 마커에서 PC 제어까지
- 21개 발견 항목 중
AV1 RTP depacketizer의 힙 버퍼 오버플로가 네트워크에서 특수 플래그 없이 도달 가능함 - 피해자는
ffmpeg -i rtsp://attacker/stream
만 실행하면 되고, 단일 183바이트 패킷으로 실행 흐름을 바꿀 수 있음
- FFmpeg가 RTSP 스트림을 가져올 때 서버는 인코딩된 비디오를 RTP 패킷 시퀀스로 전달함
- AV1은 비트스트림을 OBU(Open Bitstream Units)로 구성하고, RTP payload format은 이 OBU를 패킷 여러 개로 나눔
- FFmpeg의 depacketizer는 나뉜 OBU를 다시 깨끗한 elementary stream으로 이어 붙이는 역할을 함
- Temporal Delimiter(TD)는 한 temporal unit, 즉 프레임과 다음 프레임을 구분하는 작은 마커임
- 사양은 payload 안의 TD를 depacketizer가 “ignore and remove”해야 한다고 정함
- 이 “ignore and remove” 처리가 문제 지점이 됐고, 에이전트가 해당 지점을 포착함
근본 원인
- depacketizer는 출력 패킷을 점진적으로 만들며,
pktpos
커서가 pkt->data
안에서 다음 바이트를 쓸 위치를 추적함
pktpos
는 패킷의 현재 끝에서 시작함
// libavformat/rtpdec_av1.c:199
pktpos = pkt->size;
- 코드가 패킷 안의 OBU element를 순회할 때 실제로 내보내는 모든 바이트 앞에는
av_grow_packet
호출이 붙고, 이 호출이 pkt->data
의 힙 할당을 키움
- 전체 루틴이 의존하는 불변식은
pktpos
가 pkt->data
의 할당 크기보다 앞서 나가면 안 된다는 점임
- Temporal Delimiter 처리 코드는 이 불변식을 깨뜨림
// libavformat/rtpdec_av1.c:250
if ((obu_type == AV1_OBU_TEMPORAL_DELIMITER) ||
(obu_type == AV1_OBU_TILE_LIST)) {
pktpos += obu_size;
rem_pkt_size -= obu_size;
obu_cnt++;
continue;
}
- TD를 건너뛸 때
pktpos
는 공격자가 선언한 obu_size
만큼 앞으로 이동하지만, 그 이동을 뒷받침할 메모리는 할당되지 않음
- 입력 포인터
buf_ptr
도 TD 바이트 뒤로 이동하지 않음
- TD의
obu_size = 148
이면 pktpos
는 148이 되지만, pkt->data
는 여전히 할당되지 않았거나 148바이트보다 훨씬 작을 수 있음
- 다음 반복은 TD 자체의 바이트를 다시 파싱하고, TD header byte를 새로운 OBU 길이로 다시 읽으며, payload를 조작된 OBU의 내용으로 사용함
- 다음 정상 OBU에서 패킷은 해당 OBU 크기만큼만 커짐
// libavformat/rtpdec_av1.c:296
if ((result = av_grow_packet(pkt, output_size)) < 0)
return result;
// libavformat/rtpdec_av1.c:304 / 336
pkt->data[pktpos++] = *buf_ptr++ | AV1F_OBU_HAS_SIZE_FIELD;
memcpy(pkt->data + pktpos, buf_ptr, obu_payload_size);
- 조작된 OBU가 17바이트이면
av_grow_packet
은 17바이트와 FFmpeg의 64바이트 입력 패딩을 더해 81바이트 버퍼를 할당함
- 쓰기는
pkt->data[148]
에서 시작해 할당 끝보다 67바이트 뒤에서 발생함
- 이 결함은 공격자가 오프셋과 내용을 모두 제어하는
힙 버퍼 오버플로가 됨
악용 방식
- 제어 가능한 오버플로가 유용하려면 버퍼 바로 뒤에 덮어쓸 만한 대상이 있어야 하며, FFmpeg의 allocator가 해당 대상을 제공함
av_grow_packet
이 패킷 데이터 버퍼를 할당할 때 av_buffer_alloc
을 거치고, 이 함수는 데이터 버퍼, AVBuffer
bookkeeping struct, AVBufferRef
를 순서대로 힙에 할당함
- FFmpeg는 64바이트 정렬의
posix_memalign
을 통해 할당하므로, 81바이트 데이터 버퍼는 128바이트 chunk를 차지하고 AVBuffer
struct가 바로 뒤에 놓임
AVBuffer
struct에는 해제 콜백으로 쓰이는 함수 포인터가 있음
// libavutil/buffer_internal.h
struct AVBuffer {
uint8_t *data; // +0
size_t size; // +8
atomic_uint refcount; // +16
void (*free)(void *opaque, uint8_t *data); // +24
void *opaque; // +32
...
};
- 데이터 버퍼 시작점 기준
AVBuffer.free
포인터는 오프셋 152에 위치함
- TD의
obu_size = 148
이면 쓰기는 pkt->data[148]
에서 시작함
- TD header byte
0x10
은 길이 16으로 다시 해석되고, 조작된 16바이트 OBU의 header와 payload가 오프셋 148부터 쓰임
AVBuffer.refcount
는 오프셋 144–147에 있어 쓰기 시작점 148보다 앞에 남고, 원래 값 1을 유지함
- 익스플로잇은 TD payload 안에 세 번째 조작 OBU를 넣어 한 번 더
av_grow_packet
을 발생시킴
- 버퍼가
av_buffer_realloc
이 아니라 av_buffer_alloc
으로 만들어졌기 때문에 reallocatable로 표시되지 않고, FFmpeg는 새 버퍼를 할당한 뒤 기존 버퍼를 해제하는 경로를 택함
// libavutil/buffer.c:209
if (!(buf->buffer->flags_internal & BUFFER_FLAG_REALLOCATABLE) || ...) {
ret = av_buffer_realloc(&new, size);
memcpy(new->data, buf->data, ...);
buffer_replace(pbuf, &new);
return 0;
}
buffer_replace
는 기존 버퍼의 refcount를 1에서 0으로 줄이고, 해제 콜백을 호출함
// libavutil/buffer.c:129
if (atomic_fetch_sub_explicit(&b->refcount, 1, memory_order_acq_rel) == 1) {
b->free(b->opaque, b->data);
}
- 이 시점에 덮어쓴
free
포인터가 호출되고, instruction pointer 제어가 가능해짐
- release build에서 단일 183바이트 RTP 패킷은
rip
을 0xdeadbeef
로 만들었음
#0 0x00000000deadbeef in ?? ()
rip 0xdeadbeef 0xdeadbeef
#1 buffer_replace (buffer.c:133)
#2 av_buffer_realloc (buffer.c:220)
#3 av_grow_packet (packet.c:151)
#4 av1_handle_packet (rtpdec_av1.c:296)
#5 rtp_parse_packet_internal (rtpdec.c:743)
영향 범위와 PoC
- 공격자가 영향을 줄 수 있는 RTSP URL을 FFmpeg가 열도록 하는 배포 환경은 이 취약점에 노출됨
- 사용자 제공 stream URL을 가져오는
미디어 ingest pipeline이 영향 범위에 해당함 - RTSP feed를 가져오는
surveillance 및 CCTV 시스템이 영향 범위에 해당함 - 원격 AV1-over-RTP 소스를 처리하는 transcoding service가 영향 범위에 해당함
- 인증이나 스트림 열기 이상의 사용자 상호작용이 필요하지 않으며, 특이한 command-line flag도 필요하지 않음
- 취약점은 이러한 클라이언트가 설계상 수행하는 일반 RTSP
PLAY
단계에서 트리거됨
- PoC 코드는 ffmpeg-dfvuln127에 있음
AI 자동 생성 콘텐츠
본 콘텐츠는 GeekNews의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기