본문으로 건너뛰기

© 2026 Molayo

GeekNews헤드라인2026. 06. 14. 08:36

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-39212ffmpeg_opt.c

에서 프리셋 파일이 깊이 제한 없이 옵션 파싱을 재귀적으로 트리거할 수 있었던 스택 오버플로이며, 2025년 7월 회귀로 들어감
CVE-2026-39213은 yuv4mpegenc rawvideo 입력 경로에서 패킷 크기에 대한 차원 검증이 빠진 힙 버퍼 오버플로이며, 2023년에 들어감
CVE-2026-39214는 원래 SDT 구현에서 남은 공간을 추적하지 않고 서비스 엔트리를 쓰는 스택 버퍼 오버플로이며, 2003년에 들어가 23년 동안 잠재 상태였음
CVE-2026-39215update_mb_info()

안의 로직 오류로 이후 호출이 할당 버퍼 뒤 12바이트를 쓰게 되는 힙 버퍼 오버플로이며, 2012년에 들어감
CVE-2026-39216img2enc.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가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0